diff --git a/config/db/update_v21_to_v22.sql b/config/db/update_v21_to_v22.sql new file mode 100644 index 0000000..d1bffe6 --- /dev/null +++ b/config/db/update_v21_to_v22.sql @@ -0,0 +1,5 @@ +ALTER TABLE mappings ADD COLUMN default_map BOOLEAN DEFAULT 0 AFTER id_project; +ALTER TABLE mappings ADD CONSTRAINT default_on_generic_map_only CHECK (default_map = 0 OR id_project IS NULL); +UPDATE mappings SET default_map = 1 WHERE id_map = (select id_map from maps where codename = 'satellite'); +UPDATE maps SET token = substring(pattern, locate('token=', pattern) + 6) WHERE codename = 'static_marker'; +UPDATE maps SET pattern = replace(pattern, token, '{token}') WHERE codename = 'static_marker'; \ No newline at end of file diff --git a/lib/Map.php b/lib/Map.php index 4a2db6a..944af67 100644 --- a/lib/Map.php +++ b/lib/Map.php @@ -11,13 +11,12 @@ class Map extends PhpObject { const MAPPING_TABLE = 'mappings'; private Db $oDb; - private $asMaps; public function __construct(Db &$oDb) { parent::__construct(__CLASS__); $this->oDb = &$oDb; - $this->setMaps(); + $this->asMaps = array(); } private function setMaps() { @@ -25,14 +24,36 @@ class Map extends PhpObject { foreach($asMaps as $asMap) $this->asMaps[$asMap['codename']] = $asMap; } + private function getMaps($sCodeName='') { + if(empty($this->asMaps)) $this->setMaps(); + return ($sCodeName=='')?$this->asMaps:$this->asMaps[$sCodeName]; + } + public function getProjectMaps($iProjectId) { - $asMappings = $this->oDb->getArrayQuery("SELECT id_map FROM mappings WHERE id_project = ".$iProjectId." OR id_project IS NULL", true); - return array_filter($this->asMaps, function($asMap) use($asMappings) {return in_array($asMap['id_map'], $asMappings);}); + $asMappings = $this->oDb->selectRows( + array( + 'select' => array(Db::getId(self::MAP_TABLE), 'default_map'), + 'from' => self::MAPPING_TABLE, + 'constraint'=> array("IFNULL(id_project, {$iProjectId})" => $iProjectId) + ), + Db::getId(self::MAP_TABLE) + ); + + $asProjectMaps = array(); + foreach($this->getMaps() as $asMap) { + if(array_key_exists($asMap['id_map'], $asMappings)) { + $asMap['default_map'] = $asMappings[$asMap['id_map']]; + $asProjectMaps[] = $asMap; + } + } + + return $asProjectMaps; } public function getMapUrl($sCodeName, $asParams) { - $asParams['token'] = $this->asMaps[$sCodeName]['token']; - return self::populateParams($this->asMaps[$sCodeName]['pattern'], $asParams); + $asMap = $this->getMaps($sCodeName); + $asParams['token'] = $asMap['token']; + return self::populateParams($asMap['pattern'], $asParams); } private static function populateParams($sUrl, $asParams) { diff --git a/lib/Media.php b/lib/Media.php index 04f915a..bb867df 100644 --- a/lib/Media.php +++ b/lib/Media.php @@ -17,30 +17,21 @@ class Media extends PhpObject { const THUMB_MAX_WIDTH = 400; - /** - * Database Handle - * @var Db - */ - private $oDb; - - /** - * Media Project - * @var Project - */ - private $oProject; + private Db $oDb; + private Project $oProject; private $asMedia; private $asMedias; - private $sSystemType; + //private $sSystemType; private $iMediaId; - public function __construct(Db &$oDb, &$oProject, $iMediaId=0) { + public function __construct(Db &$oDb, Project &$oProject, $iMediaId=0) { parent::__construct(__CLASS__); $this->oDb = &$oDb; $this->oProject = &$oProject; $this->asMedia = array(); $this->asMedias = array(); - $this->sSystemType = (substr(php_uname(), 0, 7) == "Windows")?'win':'unix'; + //$this->sSystemType = (substr(php_uname(), 0, 7) == "Windows")?'win':'unix'; $this->setMediaId($iMediaId); } diff --git a/lib/Spot.php b/lib/Spot.php index aad887d..573c973 100755 --- a/lib/Spot.php +++ b/lib/Spot.php @@ -150,7 +150,8 @@ class Spot extends Main Project::PROJ_TABLE => "UNIQUE KEY `uni_proj_name` (`codename`)", Media::MEDIA_TABLE => "UNIQUE KEY `uni_file_name` (`filename`)", User::USER_TABLE => "UNIQUE KEY `uni_email` (`email`)", - Map::MAP_TABLE => "UNIQUE KEY `uni_map_name` (`codename`)" + Map::MAP_TABLE => "UNIQUE KEY `uni_map_name` (`codename`)", + Map::MAPPING_TABLE => "default_on_generic_map_only CHECK (`default_map` = 0 OR `id_project` IS NULL)" ), 'cascading_delete' => array ( diff --git a/readme.md b/readme.md index 7e23da1..9299293 100644 --- a/readme.md +++ b/readme.md @@ -27,9 +27,8 @@ 7. Go to #admin and create a new project, feed & maps 8. Add a GPX file named .gpx to /geo/ ## To Do List -* ECMA import/export * Add mail frequency slider * Use WMTS servers directly when not using Geo Caching Server * Allow HEIF picture format -* Vector tiles support (https://www.arcgis.com/home/item.html?id=7dc6cea0b1764a1f9af2e679f642f0f5) + Use of GL library. Use Mapbox GL JS / Maplibre GL JS / ESRI-Leaflet-vector? -* Fix .MOV playback on windows firefox \ No newline at end of file +* Fix .MOV playback on windows firefox +* Garmin InReach Integration \ No newline at end of file diff --git a/src/Spot.vue b/src/Spot.vue index 408f2e3..e58dd74 100644 --- a/src/Spot.vue +++ b/src/Spot.vue @@ -13,6 +13,11 @@ export default { hash: {} }; }, + provide() { + return { + projects: this.spot.vars('projects') + }; + }, inject: ['spot'], computed: { page() { @@ -36,7 +41,10 @@ export default { }, onHashChange() { let asHash = this.getHash(); - if(asHash.hash !='' && asHash.page != '') this.hash = asHash; + if(asHash.hash !='' && asHash.page != '') { + if(asHash.page == this.hash.page) this.spot.onSamePageMove(asHash); + this.hash = asHash; + } else if(!this.hash.page) this.setHash(this.spot.consts.default_page); }, getHash() { diff --git a/src/components/project.vue b/src/components/project.vue index c87c48f..f2f0c7d 100644 --- a/src/components/project.vue +++ b/src/components/project.vue @@ -31,12 +31,14 @@ export default { settingsPanelOpen: false, markerSize: {width: 32, height: 32}, project: {}, + projectCodename: null, modeHisto: false, posts: [], nlFeedbacks: [], nlLoading: false, user: {name:'', email:''}, - maps: {}, + baseMaps: {}, + baseMap: null, map: {}, hikeTypes: ['main', 'off-track', 'hitchhiking'] }; @@ -64,42 +66,56 @@ export default { return this.spot.isMobile(); } }, + watch: { + baseMap(sNewBaseMap, sOldBaseMap) { + if(sOldBaseMap) this.map.setLayoutProperty(sOldBaseMap, 'visibility', 'none'); + this.map.setLayoutProperty(sNewBaseMap, 'visibility', 'visible'); + }, + projectCodename(sNewCodeName, sOldCodeName) { + console.log('change in projectCodename: '+sNewCodeName); + //this.toggleSettingsPanel(false); + this.$parent.setHash(this.$parent.hash.page, [sNewCodeName]); + this.init(); + } + }, provide() { return { user: this.user, project: this.project }; }, - inject: ['spot'], + inject: ['spot', 'projects'], mounted() { - //Set default project - if(this.$parent.hash.items.length==0) this.$parent.setHash(this.$parent.hash.page, [this.spot.vars('default_project_codename')]); - else { - this.initEvents(); - this.initProject(); - this.initLightbox(); - this.initFeed(); - this.initSettings(); - this.initMap(); - } + this.spot.addPage('project', { + onResize: () => { + //this.spot.tmp('map_offset', -1 * (this.feedPanelOpen?getOuterWidth(this.$refs.feed):0) / getOuterWidth(window)); + + /* TODO + if(typeof this.spot.tmp('elev') != 'undefined' && this.spot.tmp('elev')._showState) { + this.spot.tmp('elev').resize({width:this.getElevWidth()}); + } + */ + } + }); + + this.projectCodename = (this.$parent.hash.items.length==0)?this.spot.vars('default_project_codename'):this.$parent.hash.items[0]; }, methods: { - initEvents() { - this.spot.addPage('project', { - onResize() { - //this.spot.tmp('map_offset', -1 * (this.feedPanelOpen?getOuterWidth(this.$refs.feed):0) / getOuterWidth(window)); - - /* TODO - if(typeof this.spot.tmp('elev') != 'undefined' && this.spot.tmp('elev')._showState) { - this.spot.tmp('elev').resize({width:this.getElevWidth()}); - } - */ - } - }); + init() { + let bFirstLoad = (typeof this.project.codename == 'undefined'); + this.initProject(); + if(bFirstLoad) this.initLightbox(); + if(bFirstLoad) this.initFeed(); + if(bFirstLoad) this.initSettings(); + this.initMap(); }, initProject() { - this.project = this.spot.vars(['projects', this.$parent.hash.items[0]]); + this.project = this.projects[this.projectCodename]; this.modeHisto = (this.project.mode == this.spot.consts.modes.histo); + this.feed = {loading:false, updatable:true, outOfData:false, refIdFirst:0, refIdLast:0, firstChunk:true}; + this.posts = []; + //this.baseMap = null; + this.baseMaps = {}; }, initLightbox() { lightbox.option({ @@ -134,6 +150,63 @@ export default { }, initSettings() { this.user = this.spot.vars('user'); + }, + async initMap() { + //Get Map Info + const aoMarkers = await this.spot.get2('markers', {id_project: this.project.id}); + this.baseMaps = aoMarkers.maps; + + //Base maps (raster tiles) + let asSources = {}; + let asLayers = []; + for(const asBaseMap of this.baseMaps) { + asSources[asBaseMap.codename] = { + type: 'raster', + tiles: [asBaseMap.pattern], + tileSize: asBaseMap.tile_size + }; + asLayers.push({ + id: asBaseMap.codename, + type: 'raster', + source: asBaseMap.codename, + 'layout': {'visibility': 'none'}, + minZoom: asBaseMap.min_zoom, + maxZoom: asBaseMap.max_zoom + }); + } + + //Map + this.map = new Map({ + container: 'map', + style: { + version: 8, + sources: asSources, + layers: asLayers + }, + center: [-122.45427081556572, 42.17865477384241], + zoom: 5, + attributionControl: false + }); + + new Marker() + .setLngLat([-122.45427081556572, 42.17865477384241]) + .addTo(this.map); + + //Toggle only when map is ready, for the tilt effet + this.toggleFeedPanel(!this.mobile); + + this.map.once('load', () => { + this.baseMap = this.baseMaps.filter((asBM)=>asBM.default_map)[0].codename; + }); + + this.map.on('idle', () => { + + }); + + //Legend + + + }, async getNextFeed() { if(!this.feed.outOfData && !this.feed.loading) { @@ -174,96 +247,12 @@ export default { }) .catch((sDesc) => {this.nlFeedbacks.push('error', sDesc);}); } - }, - async initMap() { - /* - let asSources = {}; - let asLayers = {}; - for (const [sMapId, asMap] of Object.entries(aoMarkers.maps)) { - asSources.push({ - type: 'raster', - tiles: [asMap.pattern], - tileSize: asMap.tile_size - }); - asLayers.push({ - - }); - } - */ - - const aoMarkers = await this.spot.get2('markers', {id_project: this.project.id}); - - this.map = new Map({ - container: 'map', - style: { - version: 8, - sources: { - satellite: { - type: 'raster', - tiles: [aoMarkers.maps.satellite.pattern], - tileSize: aoMarkers.maps.satellite.tile_size - } - }, - layers: [ - { - id: 'satellite', - type: 'raster', - source: 'satellite', - minzoom: 0, - maxzoom: 18 - } - ] - }, - center: [-122.45427081556572, 42.17865477384241], - zoom: 5, - attributionControl: false - }); - - new Marker() - .setLngLat([-122.45427081556572, 42.17865477384241]) - .addTo(this.map); - - this.toggleFeedPanel(!this.mobile); - - //Raster Tiles - this.map.once('load', async () => { - //const aoMarkers = await this.spot.get2('markers', {id_project: this.project.id}); - this.maps = aoMarkers.maps; - for (const [sMapId, asMap] of Object.entries(this.maps)) { - if(sMapId=='satellite') continue; - - this.map.addSource(sMapId, { - type: 'raster', - tiles: [asMap.pattern], - tileSize: asMap.tile_size - }); - - this.map.addLayer({ - id: sMapId, - type: 'raster', - source: sMapId, - 'layout': { - 'visibility': 'none' - }, - minZoom: asMap.min_zoom, - maxZoom: asMap.max_zoom - }); - } - }); - this.map.on('idle', () => { - - }); - - //Legend - - - }, toggleFeedPanel(bShow, sMapAction) { let bOldValue = this.feedPanelOpen; this.feedPanelOpen = (typeof bShow === 'object' || typeof bShow === 'undefined')?(!this.feedPanelOpen):bShow; - if(bOldValue != this.feedPanelOpen) { + if(bOldValue != this.feedPanelOpen && !this.mobile) { this.spot.onResize(); sMapAction = sMapAction || 'panTo'; @@ -297,7 +286,7 @@ export default { let bOldValue = this.settingsPanelOpen; this.settingsPanelOpen = (typeof bShow === 'object' || typeof bShow === 'undefined')?(!this.settingsPanelOpen):bShow; - if(bOldValue != this.settingsPanelOpen) { + if(bOldValue != this.settingsPanelOpen && !this.mobile) { this.spot.onResize(); sMapAction = sMapAction || 'panTo'; @@ -361,20 +350,30 @@ export default {
-

-
+

+
+
+ + +
+