diff --git a/source/UI/css/UI.css b/source/UI/css/UI.css index 2b29ba4e..fcac9865 100644 --- a/source/UI/css/UI.css +++ b/source/UI/css/UI.css @@ -1821,6 +1821,7 @@ Maps top: 4px; width: 136px; text-overflow: ellipsis; + line-height: 1; } .OxMap .OxPlaceControl.OxPlaceDeselectButton { right: 4px; @@ -2969,3 +2970,20 @@ Miscellaneous .OxTooltip > div { font-size: 9px; } + +/* +================================================================================ +MapLibre GL cleanups +================================================================================ +*/ + +.maplibregl-popup-anchor-bottom .maplibregl-popup-tip { + display: none !important; +} + +.maplibregl-popup-content { + padding: 0px !important; + border-radius: 5px; + top: 50px; + left: -4px; +} diff --git a/source/UI/js/Map/Map.js b/source/UI/js/Map/Map.js index 9f5c4392..4a551da4 100644 --- a/source/UI/js/Map/Map.js +++ b/source/UI/js/Map/Map.js @@ -99,6 +99,7 @@ Ox.Map = function(options, self) { return place.name || 'Unnamed'; }, maxMarkers: 100, + nominatim: 'https://nominatim.openstreetmap.org', places: null, selected: '', showControls: false, @@ -106,6 +107,7 @@ Ox.Map = function(options, self) { showStatusbar: false, showToolbar: false, showZoombar: false, + style: 'https://tiles.openfreemap.org/styles/liberty', zoomOnlyWhenFocused: false // fixme: width, height }) @@ -143,7 +145,7 @@ Ox.Map = function(options, self) { self.map.fitBounds(mapBounds); } else { self.map.setZoom(self.minZoom); - self.map.setCenter(new google.maps.LatLng(0, 0)); + self.map.setCenter(new maplibregl.LngLat(0, 0)); } // fixme: the following is just a guess self.boundsChanged = true; @@ -509,23 +511,27 @@ Ox.Map = function(options, self) { $placeControl.css({opacity: 0}).hide(); }); - if (window.google) { + if (window.maplibregl) { // timeout needed so that the map is in the DOM setTimeout(initMap); - } else if (window.googleCallback) { - (function interval() { - isLoaded() ? initMap() : setTimeout(interval, 100); - }()); } else { - window.googleCallback = function() { - delete window.googleCallback; - initMap(); - }; - $.getScript( - document.location.protocol - + '//maps.google.com/maps/api/js?callback=googleCallback&sensor=false' - + (Ox.Map.GoogleApiKey ? '&key=' + Ox.Map.GoogleApiKey : '') - ); + Ox.getStylesheet([ + Ox.PATH + 'UI/maplibre-gl/maplibre-gl.css', + Ox.PATH + 'UI/maplibre-gl/maplibre-gl-geocoder.css' + ], () => {}) + $.getScript([ + Ox.PATH + 'UI/maplibre-gl/maplibre-gl.js', + Ox.PATH + 'UI/maplibre-gl/maplibre-gl-geocoder.min.js', + ], initMap) + } + + function equalBonds(a, b) { + return ( + a._sw.lat == b._sw.lat && + a._sw.lng == b._sw.lng && + a._ne.lat == b._ne.lat && + a._ne.lng == b._ne.lng + ) } function addPlaceToMap(place) { @@ -535,11 +541,11 @@ Ox.Map = function(options, self) { if (!place) { var bounds = self.map.getBounds(), center = self.map.getCenter(), - southwest = new google.maps.LatLngBounds( - bounds.getSouthWest(), center + southwest = new maplibregl.LngLatBounds( + bounds._sw, center ).getCenter(), - northeast = new google.maps.LatLngBounds( - center, bounds.getNorthEast() + northeast = new maplibregl.LngLatBounds( + center, bounds._ne ).getCenter(), place = new Ox.MapPlace({ alternativeNames: [], @@ -550,14 +556,14 @@ Ox.Map = function(options, self) { map: that, name: '', type: 'feature', - south: southwest.lat(), - west: southwest.lng(), - north: northeast.lat(), - east: northeast.lng() + south: southwest.lat, + west: southwest.lng, + north: northeast.lat, + east: northeast.lng }); } Ox.forEach(self.places, function(p, i) { - if (place.bounds.equals(p.bounds)) { + if (equalBonds(place.bounds, p.bounds)) { place = p; exists = true; return false; // break @@ -593,13 +599,12 @@ Ox.Map = function(options, self) { // checks if outerBounds _can_ contain innerBounds var outerSpan = outerBounds.toSpan(), innerSpan = innerBounds.toSpan(); - return outerSpan.lat() > innerSpan.lat() && - outerSpan.lng() > innerSpan.lng(); + return outerSpan.lat > innerSpan.lat && + outerSpan.lng > innerSpan.lng; } function centerChanged() { - var tooltip = $('.OxMapMarkerTooltip'); - tooltip.length && Ox.$elements[$(tooltip[0]).data('oxid')].hide(); + that.tooltip.remove() self.center = self.map.getCenter(); self.centerChanged = true; } @@ -611,7 +616,7 @@ Ox.Map = function(options, self) { function clickMap(event) { var place = getSelectedPlace(); if (self.options.clickable/* && !editing()*/) { - getPlaceByLatLng(event.latLng, self.map.getBounds(), function(place) { + getPlaceByLatLng(event.lngLat, self.map.getBounds(), function(place) { if (place) { addPlaceToMap(place); //selectPlace(place.id); @@ -656,7 +661,7 @@ Ox.Map = function(options, self) { function crossesDateline() { var bounds = self.map.getBounds(); - return bounds.getSouthWest().lng() > bounds.getNorthEast().lng(); + return bounds._sw.lng > bounds._ne.lng; } function editing() { @@ -681,9 +686,9 @@ Ox.Map = function(options, self) { // get initial map bounds self.options.places({}, function(result) { var area = result.data.area; - callback(new google.maps.LatLngBounds( - new google.maps.LatLng(area.south, area.west), - new google.maps.LatLng(area.north, area.east) + callback(new maplibregl.LngLatBounds( + new maplibregl.LngLat(area.west, area.south), + new maplibregl.LngLat(area.east, area.north) )); }); } @@ -704,15 +709,22 @@ Ox.Map = function(options, self) { callback = point; point = self.map.getCenter(); } + let maxZoom = self.map.getMaxZoom() + setTimeout(() => { + callback(maxZoom) + }) + /* self.maxZoomService.getMaxZoomAtLatLng(point, function(data) { callback(data.status == 'OK' ? data.zoom : null); }); + */ } function getMetersPerPixel() { // m/px = m/deg * deg/px - var degreesPerPixel = 360 / (self.tileSize * Math.pow(2, self.map.getZoom())); - return Ox.getMetersPerDegree(self.map.getCenter().lat()) * degreesPerPixel; + const degreesPerPixel = 360 / (self.tileSize * Math.pow(2, self.map.getZoom())); + const center = self.map.getCenter(); + return Ox.getMetersPerDegree(center.lat) * degreesPerPixel; } function getMinZoom() { @@ -734,61 +746,50 @@ Ox.Map = function(options, self) { : Ox.getObjectById(self.places, id); } - function getPlaceByLatLng(latlng, bounds, callback) { + async function getPlaceByLatLng(latlng, bounds, callback) { // gets the largest place at latlng that would fit in bounds var callback = arguments.length == 3 ? callback : bounds, bounds = arguments.length == 3 ? bounds : null; self.$loadingIcon && self.$loadingIcon.start(); - self.geocoder.geocode({ - latLng: latlng - }, function(results, status) { - self.$loadingIcon && self.$loadingIcon.stop(); - if (status == google.maps.GeocoderStatus.OK) { - if (bounds) { - Ox.forEach(results.reverse(), function(result, i) { - if ( - i == results.length - 1 || - canContain(bounds, result.geometry.bounds || result.geometry.viewport) - ) { - callback(new Ox.MapPlace(parseGeodata(results[i]))); - return false; // break - } - }); - } else { - callback(new Ox.MapPlace(parseGeodata(results[0]))); - } - } - if ( - status == google.maps.GeocoderStatus.OK || - status == google.maps.GeocoderStatus.ZERO_RESULTS - ) { - triggerGeocodeEvent({ - latLng: latlng, - results: results + var results = await reverseGeocode(latlng); + self.$loadingIcon && self.$loadingIcon.stop(); + if (true) { + if (bounds) { + Ox.forEach(results, function(result, i) { + if ( + i == results.length - 1 || + canContain(bounds, result.geometry.bounds || result.geometry.viewport) + ) { + callback(new Ox.MapPlace(parseGeodata(results[i]))); + return false; // break + } }); } else { - Ox.Log('Map', 'geocode failed:', status); - callback(null); + callback(new Ox.MapPlace(parseGeodata(results[0]))); } - }); + } + if (!results.length) { + triggerGeocodeEvent({ + latLng: latlng, + results: results + }); + } else { + Ox.Log('Map', 'geocode failed:', status); + callback(null); + } } function getPlaceByName(name, callback) { self.$loadingIcon && self.$loadingIcon.start(); - self.geocoder.geocode({ - address: name - }, function(results, status) { + forwardGeocode({ + query: name + }).then(function(results) { self.$loadingIcon && self.$loadingIcon.stop(); - if (status == google.maps.GeocoderStatus.OK) { - callback(new Ox.MapPlace(parseGeodata(results[0]))); - } - if ( - status == google.maps.GeocoderStatus.OK - && status != google.maps.GeocoderStatus.ZERO_RESULTS - ) { + if (results.features.length) { + callback(new Ox.MapPlace(parseGeodata(results.features[0]))); triggerGeocodeEvent({ address: name, - results: results + results: results.features }); } else { Ox.Log('Map', 'geocode failed:', status); @@ -831,36 +832,167 @@ Ox.Map = function(options, self) { : null; } + function bbox2bounds(bbox) { + return new maplibregl.LngLatBounds( + new maplibregl.LngLat(bbox[0], bbox[1]), + new maplibregl.LngLat(bbox[2], bbox[3]) + ) + } + + function converNominatimFeature(feature) { + const center = [ + feature.bbox[0] + + (feature.bbox[2] - feature.bbox[0]) / 2, + feature.bbox[1] + + (feature.bbox[3] - feature.bbox[1]) / 2 + ]; + const polygon = { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [feature.bbox[0], feature.bbox[1]], + [feature.bbox[1], feature.bbox[2]], + [feature.bbox[2], feature.bbox[3]], + [feature.bbox[0], feature.bbox[3]], + ] + }, + bounds: bbox2bounds(feature.bbox), + place_name: feature.properties.display_name, + properties: feature.properties, + text: feature.properties.display_name, + address_components: [ + { + long_name: feature.properties.display_name, + short_name: feature.properties.display_name, + types: ['place'] + } + ], + place_type: ['place'], + }; + return polygon + } + + async function reverseGeocode(config) { + const features = []; + try { + const request = `${self.options.nominatim}/reverse?lat=${ + config.lat + }&lon=${ + config.lng + }&format=geojson&polygon_geojson=1&addressdetails=1`; + const response = await fetch(request); + const geojson = await response.json(); + for (const feature of geojson.features) { + features.push(converNominatimFeature(feature)); + } + } catch (e) { + console.error(`Failed to reverseGeocode with error: ${e}`); + } + return { + features + }; + } + async function forwardGeocode(config) { + const features = []; + try { + const request = `${self.options.nominatim}/search?q=${ + config.query + }&format=geojson&polygon_geojson=1&addressdetails=1`; + const response = await fetch(request); + const geojson = await response.json(); + for (const feature of geojson.features) { + features.push(converNominatimFeature(feature)); + } + } catch (e) { + console.error(`Failed to forwardGeocode with error: ${e}`); + } + + return { + features + }; + } + function initMap() { getMapBounds(function(mapBounds) { - //Ox.Log('Map', 'init', mapBounds.getSouthWest(), mapBounds.getNorthEast(), mapBounds.getCenter()) + //Ox.Log('Map', 'init', mapBounds._sw, mapBounds._ne, mapBounds.getCenter()) - self.elevationService = new google.maps.ElevationService(); - self.geocoder = new google.maps.Geocoder(); - self.maxZoomService = new google.maps.MaxZoomService(); + //self.elevationService = new google.maps.ElevationService(); + //self.maxZoomService = new google.maps.MaxZoomService(); + // - self.center = mapBounds ? mapBounds.getCenter() : new google.maps.LatLng(0, 0); + self.center = mapBounds ? mapBounds.getCenter() : new maplibregl.LngLat(0, 0); self.zoom = self.minZoom; - that.map = self.map = new google.maps.Map(self.$map[0], { + window.map = that.map = self.map = new maplibregl.Map({ + container: self.$map[0], center: self.center, - disableDefaultUI: true, - disableDoubleClickZoom: true, - mapTypeId: google.maps.MapTypeId[getMapType()], - noClear: true, - scrollwheel: !self.options.zoomOnlyWhenFocused, + style2: self.options.style, + style: { + 'version': 8, + 'sources': { + 'raster-tiles': { + 'type': 'raster', + 'tiles': [ + // FIXME: use protocol and make use of all subdomains + //'https://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}' + 'https://mt0.google.com/vt/lyrs=s&x={x}&y={y}&z={z}' + + ], + 'subdomains':['mt0','mt1','mt2','mt3'], + 'tileSize': 256, + 'attribution': + 'FIXME', + } + }, + 'layers': [ + { + 'id': 'simple-tiles', + 'type': 'raster', + 'source': 'raster-tiles', + 'minzoom': 0, + 'maxzoom': 22 + } + ] + }, + //mapTypeId: google.maps.MapTypeId[getMapType()], + //noClear: true, + //scrollwheel: !self.options.zoomOnlyWhenFocused, zoom: self.zoom + }) + self.geocoder = new MaplibreGeocoder({ + forwardGeocode: forwardGeocode, + reverseGeocode: reverseGeocode, + }) + /* + map.addControl(self.geocoder, { + maplibregl }); + */ + that.map.on('click', clickMap) + that.map.on('zoom', zoomChanged) + that.map.on('idle', mapChanged) + that.map.on('moveend', boundsChanged) + that.map.on('dragend', boundsChanged) + that.map.on('zoomend', boundsChanged) + /* + that.map.on('resize', () => { + that.resizeMap() + }) + */ + /* google.maps.event.addListener(self.map, 'bounds_changed', boundsChanged); google.maps.event.addListener(self.map, 'center_changed', centerChanged); google.maps.event.addListener(self.map, 'click', clickMap); google.maps.event.addListener(self.map, 'idle', mapChanged); google.maps.event.addListener(self.map, 'zoom_changed', zoomChanged); google.maps.event.trigger(self.map, 'resize'); + */ // needed to get mouse x/y coordinates on marker mouseover, // see http://code.google.com/p/gmaps-api-issues/issues/detail?id=2342 + /* that.overlayView = new google.maps.OverlayView(); that.overlayView.setMap(self.map); that.overlayView.draw = function () { @@ -870,52 +1002,55 @@ Ox.Map = function(options, self) { } } that.overlayView.draw(); + */ - Ox.forEach(self.$controls, function($control) { - $control.appendTo(self.$map); - }); - Ox.forEach(self.$placeControls, function($placeControl) { - $placeControl.appendTo(self.$map); - }); + that.map.on('load', () => { + Ox.forEach(self.$controls, function($control) { + $control.appendTo(self.$map); + }); + Ox.forEach(self.$placeControls, function($placeControl) { + $placeControl.appendTo(self.$map); + }); - if (self.options.find) { - self.$findInput - .value(self.options.find) - .triggerEvent('submit', {value: self.options.find}); - } else { - if (self.options.selected) { - selectPlace(self.options.selected, true); - } - if (mapBounds) { - if (isEmpty(mapBounds)) { + if (self.options.find) { + self.$findInput + .value(self.options.find) + .triggerEvent('submit', {value: self.options.find}); + } else { + if (self.options.selected) { + selectPlace(self.options.selected, true); + } + if (mapBounds) { + if (isEmpty(mapBounds)) { + self.map.setZoom(self.minZoom); + } else { + self.map.fitBounds(mapBounds); + + } + } + if (self.map.getZoom() < self.minZoom) { self.map.setZoom(self.minZoom); - } else { - self.map.fitBounds(mapBounds); } } - if (self.map.getZoom() < self.minZoom) { - self.map.setZoom(self.minZoom); - } - } - updateFormElements(); + updateFormElements(); - self.loaded = true; - that.triggerEvent('load'); + that.resizeMap() + self.loaded = true; + self.boundsChanged = true; + that.triggerEvent('load'); + }) }); } function isEmpty(bounds) { - // Google's bounds.isEmpty() is not reliable - var southWest = bounds.getSouthWest(), - northEast = bounds.getNorthEast(); - return southWest.lat() == northEast.lat() - && southWest.lng() == northEast.lng(); + return bounds._sw.lat == bounds._ne.lat + && bounds._sw.lng == bounds._ne.lng; } function isLoaded() { - return window.google && window.google.maps && window.google.maps.LatLng; + return window.maplibregl && window.maplibregl.maps && window.maplibregl.LngLat; } function mapChanged() { @@ -927,12 +1062,11 @@ Ox.Map = function(options, self) { self.boundsChanged = false; return } - var southWest = bounds.getSouthWest(), - northEast = bounds.getNorthEast(), - south = southWest.lat(), - west = southWest.lng(), - north = northEast.lat(), - east = northEast.lng(); + var south = bounds._sw.lat, + west = bounds._sw.lng, + north = bounds._ne.lat, + east = bounds._ne.lng; + self.options.places({ keys: self.placeKeys, query: { @@ -1015,22 +1149,24 @@ Ox.Map = function(options, self) { } function parseGeodata(data) { - var bounds = data.geometry.bounds || data.geometry.viewport, - northEast = bounds.getNorthEast(), - southWest = bounds.getSouthWest(), + console.log("parseGeodata", data) + // FIXME: data is geojson Feature with Polygon geometry now + var bounds = data.bounds || data.geometry.bounds || data.geometry.viewport, + northEast = bounds._ne, + southWest = bounds._sw, place = { alternativeNames: [], components: data.address_components, countryCode: getCountryCode(data.address_components), - east: northEast.lng(), + east: northEast.lng, editable: self.options.editable, fullGeoname: getFullGeoname(data.address_components), id: '_' + Ox.encodeBase32(Ox.uid()), map: that, - north: northEast.lat(), - south: southWest.lat(), + north: northEast.lat, + south: southWest.lat, type: getType(data.address_components[0].types), - west: southWest.lng() + west: southWest.lng }; place.geoname = data.formatted_address || place.fullGeoname; place.name = (place.geoname || place.fullGeoname).split(', ')[0]; @@ -1338,8 +1474,8 @@ Ox.Map = function(options, self) { // so we create properly named properties, for json encoding if (data.latLng) { data.latLng = { - lat: data.latLng.lat(), - lng: data.latLng.lng() + lat: data.latLng.lat, + lng: data.latLng.lng } } data.results.forEach(function(result) { @@ -1347,20 +1483,20 @@ Ox.Map = function(options, self) { if (result.geometry[key]) { result.geometry[key] = { northEast: { - lat: result.geometry[key].getNorthEast().lat(), - lng: result.geometry[key].getNorthEast().lng() + lat: result.geometry[key]._ne.lat, + lng: result.geometry[key]._ne.lng }, southWest: { - lat: result.geometry[key].getSouthWest().lat(), - lng: result.geometry[key].getSouthWest().lng() + lat: result.geometry[key]._sw.lat, + lng: result.geometry[key]._sw.lng } } } }); if (result.geometry.location) { result.geometry.location = { - lat: result.geometry.location.lat(), - lng: result.geometry.location.lng() + lat: result.geometry.location.lat, + lng: result.geometry.location.lng } } }); @@ -1402,7 +1538,7 @@ Ox.Map = function(options, self) { } function zoomChanged() { - var zoom = self.map.getZoom(); + var zoom = parseInt(self.map.getZoom()); if (zoom < self.minZoom) { self.map.setZoom(self.minZoom); } else if (self.maxZoom && zoom > self.maxZoom) { @@ -1443,8 +1579,7 @@ Ox.Map = function(options, self) { lng Longitude @*/ that.getCenter = function() { - var center = self.map.getCenter(); - return {lat: center.lat(), lng: center.lng()}; + return self.map.getCenter(); }; /*@ @@ -1544,7 +1679,7 @@ Ox.Map = function(options, self) { }); updateFormElements(); Ox.Log('Map', 'triggering google maps resize event, height', self.options.height) - google.maps.event.trigger(self.map, 'resize'); + self.map.triggerRepaint() // self.map.setCenter(center); } return that; @@ -1558,7 +1693,7 @@ Ox.Map = function(options, self) { lng Longitude @*/ that.setCenter = function(center) { - self.map.setCenter(new google.maps.LatLng(center.lat, center.lng)); + self.map.setCenter(new maplibregl.LngLat(center.lng, center.lat)); return that; }; diff --git a/source/UI/js/Map/MapMarker.js b/source/UI/js/Map/MapMarker.js index 10eb69eb..338f4ed3 100644 --- a/source/UI/js/Map/MapMarker.js +++ b/source/UI/js/Map/MapMarker.js @@ -42,16 +42,32 @@ Ox.MapMarker = function(options) { Ox.forEach(options, function(val, key) { that[key] = val; }); - that.marker = new google.maps.Marker({ + setColor() + const element = document.createElement('div') + element.style.border = '2px solid black' + element.style.borderRadius = '50px' + element.style.backgroundColor = '#' + Ox.toHex(that.color) + element.style.width = '16px' + element.style.height = '16px' + that.marker = new maplibregl.Marker({ raiseOnDrag: false, - shape: {coords: [8, 8, 8], type: 'circle'} + shape: {coords: [8, 8, 8], type: 'circle'}, + element: element, //title: that.place.name, //zIndex: 1000 }); + that.tooltip = new maplibregl.Popup({ + closeButton: false, + closeOnClick: false, + className: 'tooltip' + }); + that.tooltip.addClass setOptions(); - function click() { + function click(event) { + event.preventDefault() + event.stopPropagation() var key = that.map.getKey(), place, bounds, southWest, northEast; if (!that.place.selected) { @@ -62,9 +78,9 @@ Ox.MapMarker = function(options) { place = that.map.getSelectedPlace(); } if (place) { - bounds = new google.maps.LatLngBounds( - new google.maps.LatLng(place.south, place.west), - new google.maps.LatLng(place.north, place.east) + bounds = new maplibregl.LngLatBounds( + new maplibregl.LngLatBounds(place.west, place.south), + new maplibregl.LngLatBounds(place.east, place.north) ); bounds = bounds.union(that.place.bounds); southWest = bounds.getSouthWest(); @@ -79,10 +95,10 @@ Ox.MapMarker = function(options) { map: that.map, name: '', type: 'feature', - south: southWest.lat(), - west: southWest.lng(), - north: northEast.lat(), - east: northEast.lng() + south: southWest.lat, + west: southWest.lng, + north: northEast.lat, + east: northEast.lng })); } else { that.map.options({selected: that.place.id}); @@ -107,11 +123,11 @@ Ox.MapMarker = function(options) { function drag(e) { var northSouth = (that.place.north - that.place.south) / 2, lat = Ox.limit( - e.latLng.lat(), + e.lngLat.lat, Ox.MIN_LATITUDE + northSouth, Ox.MAX_LATITUDE - northSouth ), - lng = e.latLng.lng(), + lng = e.lngLat.lng, span = Math.min( that.place.sizeEastWest * Ox.getDegreesPerMeter(lat) / 2, 179.99999999 ), @@ -127,9 +143,7 @@ Ox.MapMarker = function(options) { } Ox.Log('Map', 'west', that.place.west, 'east', that.place.east, 'span', span); that.place.update(); - that.marker.setOptions({ - position: that.place.center - }); + that.marker.setLngLat(that.place.center) that.place.rectangle.update(); } @@ -181,35 +195,30 @@ Ox.MapMarker = function(options) { c.context.strokeStyle = 'rgb(' + border.join(', ') + ')'; c.context.arc(r, r, r - 1, 0, 360); c.context.stroke(); - callback(new google.maps.MarkerImage( + callback(new maplibregl.MarkerImage( c.canvas.toDataURL(), - new google.maps.Size(options.size, options.size), - new google.maps.Point(0, 0), - new google.maps.Point(r, r) + new maplibregl.Size(options.size, options.size), + new maplibregl.Point(0, 0), + new maplibregl.Point(r, r) )); } } function mouseover(e) { - var offset = that.map.offset(), - xy = that.map.overlayView.getProjection() - .fromLatLngToContainerPixel(e.latLng); - that.tooltip.show( - offset.left + Math.round(xy.x) - 4, - offset.top + Math.round(xy.y) + 20 - ); + that.tooltip.setLngLat(that.place.center).setHTML( + '
' + + '
' + + that.map.options('markerTooltip')(that.place) + '
' + ).addTo(that.map.map); } function mouseout() { - that.tooltip.hide(); + that.tooltip.remove(); } - function setOptions() { - // workaround to prevent marker from appearing twice - // after setting draggable from true to false (google maps bug) - var fix = that.marker.getDraggable() && !that.place.editing, - color = that.map.options('markerColor'), - size = that.map.options('markerSize'); + function setColor() { + var color = that.map.options('markerColor'); //Ox.Log('Map', 'setOptions, that.map: ', that.map) if (color == 'auto') { that.color = typeColor[that.place.type] || typeColor['mapPlaceFeatureColor']; @@ -218,6 +227,15 @@ Ox.MapMarker = function(options) { } else { that.color = color(that.place); } + } + + function setOptions() { + // workaround to prevent marker from appearing twice + // after setting draggable from true to false (google maps bug) + var fix = false, // that.marker.getDraggable() && !that.place.editing, + size = that.map.options('markerSize'); + //Ox.Log('Map', 'setOptions, that.map: ', that.map) + setColor() if (size == 'auto') { that.size = 8; Ox.forEach(areaSize, function(size, area) { @@ -232,50 +250,46 @@ Ox.MapMarker = function(options) { } else { that.size = size(that.place); } + /* fixme, some of those can be set some not that.marker.setOptions({ // fixme: cursor remains pointer cursor: that.place.editing ? 'move' : 'pointer', draggable: that.place.editing, - icon: Ox.MapMarkerImage({ + element: Ox.MapMarkerImage({ color: that.color, mode: that.place.editing ? 'editing' : that.place.selected ? 'selected' : 'normal', size: that.size, type: that.place.id[0] == '_' ? 'result' : 'place' }), - position: that.place.center - }); + }) + */ + //that.marker._color = that.color; + that.marker._element.style.cursor = that.place.editing ? 'move' : 'pointer'; + that.marker.setDraggable(that.place.editing); + that.marker.setLngLat(that.place.center); if (fix) { that.marker.setVisible(false); setTimeout(function() { that.marker.setVisible(true); }, 0); } - setTooltip(); } - function setTooltip() { - that.tooltip && that.tooltip.remove(); - that.tooltip = Ox.Tooltip({ - title: '' - + '
' - + that.map.options('markerTooltip')(that.place) + '
' - }) - .addClass('OxMapMarkerTooltip'); - } /*@ add add to map () -> add to map, returns MapMarker @*/ that.add = function() { - that.marker.setMap(that.map.map); - google.maps.event.addListener(that.marker, 'click', click); - google.maps.event.addListener(that.marker, 'dblclick', dblclick); - google.maps.event.addListener(that.marker, 'mouseover', mouseover); - google.maps.event.addListener(that.marker, 'mouseout', mouseout); + that.marker.addTo(that.map.map); + const element = that.marker.getElement() + if(element) { + element.addEventListener('click', click) + element.addEventListener('dblclick', dblclick) + element.addEventListener('mouseover', mouseover) + element.addEventListener('mouseout', mouseout) + } return that; }; @@ -285,9 +299,9 @@ Ox.MapMarker = function(options) { @*/ that.edit = function() { setOptions(); - google.maps.event.addListener(that.marker, 'dragstart', dragstart); - google.maps.event.addListener(that.marker, 'drag', drag); - google.maps.event.addListener(that.marker, 'dragend', dragend); + that.marker.on('dragstart', dragstart); + that.marker.on('drag', drag); + that.marker.on('dragend', dragend); return that; }; @@ -296,8 +310,12 @@ Ox.MapMarker = function(options) { () -> remove marker from map, returns MapMarker @*/ that.remove = function() { - that.marker.setMap(null); - google.maps.event.clearListeners(that.marker); + that.marker.remove(); + //that.marker.off('dragstart'); + //that.marker.off('drag'); + //that.marker.off('dragend'); + //fixme does this work to remove all events? + that.marker.off(); return that; }; @@ -306,9 +324,9 @@ Ox.MapMarker = function(options) { () -> clear edit listeners, returns MapMarker @*/ that.submit = function() { - google.maps.event.clearListeners(that.marker, 'dragstart'); - google.maps.event.clearListeners(that.marker, 'drag'); - google.maps.event.clearListeners(that.marker, 'dragend'); + that.marker.off('dragstart'); + that.marker.off('drag'); + that.marker.off('dragend'); return that; } diff --git a/source/UI/js/Map/MapMarkerImage.js b/source/UI/js/Map/MapMarkerImage.js index 51063aec..994e41e8 100644 --- a/source/UI/js/Map/MapMarkerImage.js +++ b/source/UI/js/Map/MapMarkerImage.js @@ -50,12 +50,10 @@ Ox.MapMarkerImage = (function() { c.context.strokeStyle = 'rgba(' + border.join(', ') + ')'; c.context.arc(r, r, r - 1, 0, 360); c.context.stroke(); - cache[index] = new google.maps.MarkerImage( - c.canvas.toDataURL(), - new google.maps.Size(options.size, options.size), - new google.maps.Point(0, 0), - new google.maps.Point(r, r) - ); + cache[index] = document.createElement('img') + cache[index].src = c.canvas.toDataURL() + cache[index].width = options.size + cache[index].height = options.size } return cache[index]; diff --git a/source/UI/js/Map/MapPlace.js b/source/UI/js/Map/MapPlace.js index 58c3e254..38cfa103 100644 --- a/source/UI/js/Map/MapPlace.js +++ b/source/UI/js/Map/MapPlace.js @@ -44,21 +44,24 @@ Ox.MapPlace = function(options) { update(); function update(updateMarker) { + if (that.east < 0 && that.west > 0) { + that.east += 360; + } that.points = { - ne: new google.maps.LatLng(that.north, that.east), - sw: new google.maps.LatLng(that.south, that.west) + ne: new maplibregl.LngLat(that.east, that.north), + sw: new maplibregl.LngLat(that.west, that.south) }; - that.bounds = new google.maps.LatLngBounds(that.points.sw, that.points.ne); + that.bounds = new maplibregl.LngLatBounds(that.points.sw, that.points.ne); that.center = that.bounds.getCenter(); - that.lat = that.center.lat(); - that.lng = that.center.lng(); + that.lat = that.center.lat; + that.lng = that.center.lng; Ox.extend(that.points, { - e: new google.maps.LatLng(that.lat, that.east), - s: new google.maps.LatLng(that.south, that.lng), - se: new google.maps.LatLng(that.south, that.east), - n: new google.maps.LatLng(that.north, that.lng), - nw: new google.maps.LatLng(that.north, that.west), - w: new google.maps.LatLng(that.lat, that.west) + e: new maplibregl.LngLat(that.east, that.lat), + s: new maplibregl.LngLat(that.lng, that.south), + se: new maplibregl.LngLat(that.east, that.south), + n: new maplibregl.LngLat(that.lng, that.north), + nw: new maplibregl.LngLat(that.west, that.north), + w: new maplibregl.LngLat(that.west, that.lat) }); // fixme: use bounds.toSpan() that.sizeNorthSouth = (that.north - that.south) @@ -79,8 +82,9 @@ Ox.MapPlace = function(options) { place: that }); } else if (updateMarker) { - that.marker.update(); - that.rectangle.update(); + console.log("fixme update marker") + //that.marker.update(); + //that.rectangle.update(); } } diff --git a/source/UI/js/Map/MapRectangle.js b/source/UI/js/Map/MapRectangle.js index ac43c923..5f1771a5 100644 --- a/source/UI/js/Map/MapRectangle.js +++ b/source/UI/js/Map/MapRectangle.js @@ -1,5 +1,172 @@ 'use strict'; + +class MapLibreRectangle { + constructor(options = {}) { + this.id = options.id || 'rectangle-' + Ox.uid(); + this.bounds = options.bounds; + this.draggable = options.draggable || false; + this.onclick = options.onclick || null + } + + _createRectangle() { + const coords = this._getPolygonCoordinates(); + + const rectangleGeoJSON = { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [coords] + }, + properties: { + id: this.id + } + }; + this.source = this.map.getSource('rectangles') + if (!this.source) { + this.map.addSource('rectangles', { + type: 'geojson', + data: { + type: 'FeatureCollection', + features: [] + } + }); + this.source = this.map.getSource('rectangles') + + // Add fill layer + if (!this.map.getLayer('rectangles-fill')) { + this.map.addLayer({ + id: 'rectangles-fill', + type: 'fill', + source: 'rectangles', + paint: { + 'fill-color': '#088', + 'fill-opacity': 0.3 + } + }); + } + + // Add outline layer + if (!this.map.getLayer('rectangles-outline')) { + this.map.addLayer({ + id: 'rectangles-outline', + type: 'line', + source: 'rectangles', + paint: { + 'line-color': '#000', + 'line-width': 2 + } + }); + } + } + /* + this.source._data.features.push(rectangleGeoJSON) + this.source.setData(this.source._data) + */ + } + + _getPolygonCoordinates() { + const sw = this.bounds._sw; + const ne = this.bounds._ne; + + return [ + [sw.lng, ne.lat], // NW + [ne.lng, ne.lat], // NE + [ne.lng, sw.lat], // SE + [sw.lng, sw.lat], // SW + [sw.lng, ne.lat] // Close polygon + ]; + } + + setBounds(bounds) { + this.bounds = bounds; + const coords = this._getPolygonCoordinates(); + const updatedData = { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [coords] + }, + properties: { + id: this.id + } + }; + var updated = false; + this.source._data.features.forEach(feature => { + if (feature.properties.id == this.id) { + feature.geometry = updatedData.geometry + updated = true + } + }) + if (!updated) { + this.source._data.features.push(updatedData) + } + this.source.setData(this.source._data) + } + + getBounds() { + return this.bounds; + } + + _enableDragging() { + let isDragging = false; + let startPos; + + this.map.on('mousedown', `${this.id}-fill`, (e) => { + e.preventDefault(); + isDragging = true; + startPos = e.lngLat; + this.map.getCanvas().style.cursor = 'grabbing'; + }); + + this.map.on('mousemove', (e) => { + if (!isDragging) return; + + const dx = e.lngLat.lng - startPos.lng; + const dy = e.lngLat.lat - startPos.lat; + + const sw = [this.bounds[0][0] + dx, this.bounds[0][1] + dy]; + const ne = [this.bounds[1][0] + dx, this.bounds[1][1] + dy]; + + this.setBounds([sw, ne]); + startPos = e.lngLat; + }); + + this.map.on('mouseup', () => { + if (isDragging) { + isDragging = false; + this.map.getCanvas().style.cursor = ''; + } + }); + } + _enableClicking() { + this.map.on('click', `${this.id}-fill`, e => { + if (this.onclick) { + this.onclick(e) + } + }) + } + + remove() { + this.source._data.features = this.source._data.features.filter(feature => { + return feature.properties.id != this.id + }) + this.source.setData(this.source._data) + } + + add() { + this.setBounds(this.bounds) + } + + addTo(map) { + this.map = map; + this._createRectangle(); + if (this.draggable) this._enableDragging(); + this._enableClicking(); + } +} + + /*@ Ox.MapRectangle MapRectangle Object (options) -> MapRectangle Object @@ -25,10 +192,18 @@ Ox.MapRectangle = function(options) { /*@ rectangle google.maps.Rectangle @*/ + /* that.rectangle = new google.maps.Rectangle({ clickable: true, bounds: that.place.bounds }); + */ + that.rectangle = new MapLibreRectangle({ + bounds: that.place.bounds, + }); + that.rectangle.addTo(that.map.map); + that.rectangle.onclick = click + /*@ markers array of markers @*/ @@ -42,7 +217,9 @@ Ox.MapRectangle = function(options) { setOptions(); - function click() { + function click(e) { + console.log('rectangle click', e) + return if ( that.map.options('editable') && that.place.editable @@ -64,6 +241,7 @@ Ox.MapRectangle = function(options) { ? 'mapPlaceEditingBorder' : 'mapPlaceSelectedBorder' ]); + /* that.rectangle.setOptions({ bounds: that.place.bounds, fillColor: color, @@ -72,14 +250,24 @@ Ox.MapRectangle = function(options) { strokeOpacity: that.place.id[0] == '_' ? 0.5 : 1, strokeWeight: 2 }); + */ + /* + console.log("fixme", { + bounds: that.place.bounds, + fillColor: color, + fillOpacity: that.place.editing ? 0.1 : 0, + strokeColor: color, + strokeOpacity: that.place.id[0] == '_' ? 0.5 : 1, + strokeWeight: 2 + }); + */ } /*@ add add @*/ that.add = function() { - that.rectangle.setMap(that.map.map); - google.maps.event.addListener(that.rectangle, 'click', click); + that.rectangle.add() return that; }; @@ -99,8 +287,7 @@ Ox.MapRectangle = function(options) { remove remove @*/ that.remove = function() { - that.rectangle.setMap(null); - google.maps.event.clearListeners(that.rectangle); + that.rectangle.remove(); return that; } diff --git a/source/UI/js/Map/MapRectangleMarker.js b/source/UI/js/Map/MapRectangleMarker.js index 7b7f252d..bc9c1f0b 100644 --- a/source/UI/js/Map/MapRectangleMarker.js +++ b/source/UI/js/Map/MapRectangleMarker.js @@ -23,6 +23,7 @@ Ox.MapRectangleMarker = function(options) { that[key] = val; }); + /* that.markerImage = new google.maps.MarkerImage that.marker = new google.maps.Marker({ cursor: that.position + '-resize', @@ -35,20 +36,30 @@ Ox.MapRectangleMarker = function(options) { position: that.place.points[that.position], raiseOnDrag: false }); + */ + that.marker = new maplibregl.Marker({ + cursor: that.position + '-resize', + draggable: true, + element: Ox.MapMarkerImage({ + mode: 'editing', + rectangle: true, + type: that.place.id[0] == '_' ? 'result' : 'place' + }), + }); function dragstart(e) { Ox.$body.addClass('OxDragging'); that.drag = { - lat: e.latLng.lat(), - lng: e.latLng.lng() + lat: e.lngLat.lat, + lng: e.lngLat.lng }; } function drag(e) { // fixme: implement shift+drag (center stays the same) Ox.Log('Map', e.pixel.x, e.pixel.y) - var lat = Ox.limit(e.latLng.lat(), Ox.MIN_LATITUDE, Ox.MAX_LATITUDE), - lng = e.latLng.lng(); + var lat = Ox.limit(e.lngLat.lat, Ox.MIN_LATITUDE, Ox.MAX_LATITUDE), + lng = e.lngLat.lng; that.drag = { lat: lat, lng: lng @@ -100,7 +111,7 @@ Ox.MapRectangleMarker = function(options) { remove remove @*/ that.remove = function() { - that.marker.setMap(null); + that.marker.remove(); google.maps.event.clearListeners(that.marker); };