1
0
Fork 0
forked from 0x2620/oxjs

Compare commits

...
Sign in to create a new pull request.

14 commits

Author SHA1 Message Date
j
80d3919b46 one layer per rectangle to redouce events 2025-08-06 23:31:07 +02:00
j
1bac02d24f fixup 2025-08-06 23:18:21 +02:00
j
56123e4575 map_editor example should use async code 2025-08-06 23:02:19 +02:00
j
bc174c49dd support updating MapEditor places 2025-08-06 23:01:31 +02:00
j
f3e0632f0e more fixes 2025-08-06 21:50:00 +02:00
j
bcade5f2d7 add map editor example 2025-08-06 21:49:45 +02:00
j
8dbfcc0ece fix raster subdomains 2025-08-06 19:30:08 +02:00
j
6a59ff5193 marker size 2025-08-06 19:29:16 +02:00
j
e917bce40d correct cross the 180th meridian (antimeridian) handling 2025-08-06 19:10:04 +02:00
j
66fca1fc02 dev mode 2025-08-06 19:03:10 +02:00
j
8cebad9fb4 migrate to maplibre-gl for maps 2025-08-06 19:03:10 +02:00
j
f3b8025e8e add maplibre-gl-geocoder 2025-08-06 14:02:53 +02:00
j
25cd3f6bb1 include maplibre-gl files in build 2025-08-05 19:09:19 +02:00
j
76b9517a78 add maplibre-gl.js 2025-08-05 18:50:09 +02:00
16 changed files with 1103 additions and 247 deletions

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>World Map with Countries</title>
<meta http-equiv="Keywords" content="Lists"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="shortcut icon" type="image/png" href="../../../source/UI/themes/oxlight/png/icon16.png"/>
<script type="text/javascript" src="../../../dev/Ox.js"></script>
<script type="text/javascript" src="js/example.js"></script>
<script>window.addEventListener('message', function(e) { e.origin == window.location.origin && eval('(' + e.data + ')'); });</script>
</head>
<body></body>
</html>

View file

@ -0,0 +1,87 @@
/*
In this example, we use Ox.MapEditor
*/
'use strict';
Ox.load(['UI', 'Geo'], function() {
var $storage = Ox.localStorage("map_editor")
var places = $storage('places') || []
var $map = Ox.MapEditor({
addPlace: function(place, callback) {
console.log("addPlace", place)
place.id = Ox.encodeBase26(Ox.uid())
places.push(place)
$storage("places", places)
$map.options({
places: Ox.api(places, {
geo: true,
sort: '-area'
})
})
setTimeout(() => {
callback({
status: {
code: 200
},
data: place
})
}, 200)
},
editPlace: function(place, callback) {
places.forEach(p => {
if (p.id == place.id) {
Object.assign(p, place);
place = p
}
})
$storage("places", places)
callback({
status: {
code: 200
},
data: place
})
},
getMatches: function(names, callback) {
console.log("getMatches", names)
callback(23);
},
hasMatches: true, // FIXME: getMatches is enough
height: 800,
mode: 'add', // 'define',
names: null,
places: Ox.api(places, {
geo: true,
sort: '-area'
}),
removePlace: function(place, callback) {
console.log("removePlace", place.id, places)
places = places.filter(p => { return p.id != place.id })
console.log("new places", places)
$storage("places", places)
$map.options({
places: Ox.api(places, {
geo: true,
sort: '-area'
})
})
setTimeout(() => {
callback({
status: {
code: 200
},
})
}, 100)
},
selected: '',
showControls: false,
showLabels: false,
showTypes: true,
width: 600
})
.appendTo(Ox.$body);
Ox.$window.bind({resize: $map.resizeMap});
});

View file

@ -5,7 +5,7 @@
<meta http-equiv="Keywords" content="Lists"/> <meta http-equiv="Keywords" content="Lists"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="shortcut icon" type="image/png" href="../../../source/UI/themes/oxlight/png/icon16.png"/> <link rel="shortcut icon" type="image/png" href="../../../source/UI/themes/oxlight/png/icon16.png"/>
<script type="text/javascript" src="../../../min/Ox.js"></script> <script type="text/javascript" src="../../../dev/Ox.js"></script>
<script type="text/javascript" src="js/example.js"></script> <script type="text/javascript" src="js/example.js"></script>
<script>window.addEventListener('message', function(e) { e.origin == window.location.origin && eval('(' + e.data + ')'); });</script> <script>window.addEventListener('message', function(e) { e.origin == window.location.origin && eval('(' + e.data + ')'); });</script>
</head> </head>

View file

@ -1821,6 +1821,7 @@ Maps
top: 4px; top: 4px;
width: 136px; width: 136px;
text-overflow: ellipsis; text-overflow: ellipsis;
line-height: 1;
} }
.OxMap .OxPlaceControl.OxPlaceDeselectButton { .OxMap .OxPlaceControl.OxPlaceDeselectButton {
right: 4px; right: 4px;
@ -2969,3 +2970,20 @@ Miscellaneous
.OxTooltip > div { .OxTooltip > div {
font-size: 9px; 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;
}

View file

@ -99,6 +99,7 @@ Ox.Map = function(options, self) {
return place.name || '<span class="OxLight">Unnamed</span>'; return place.name || '<span class="OxLight">Unnamed</span>';
}, },
maxMarkers: 100, maxMarkers: 100,
nominatim: 'https://nominatim.openstreetmap.org',
places: null, places: null,
selected: '', selected: '',
showControls: false, showControls: false,
@ -106,6 +107,7 @@ Ox.Map = function(options, self) {
showStatusbar: false, showStatusbar: false,
showToolbar: false, showToolbar: false,
showZoombar: false, showZoombar: false,
style: 'https://tiles.openfreemap.org/styles/liberty',
zoomOnlyWhenFocused: false zoomOnlyWhenFocused: false
// fixme: width, height // fixme: width, height
}) })
@ -143,7 +145,7 @@ Ox.Map = function(options, self) {
self.map.fitBounds(mapBounds); self.map.fitBounds(mapBounds);
} else { } else {
self.map.setZoom(self.minZoom); 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 // fixme: the following is just a guess
self.boundsChanged = true; self.boundsChanged = true;
@ -509,23 +511,27 @@ Ox.Map = function(options, self) {
$placeControl.css({opacity: 0}).hide(); $placeControl.css({opacity: 0}).hide();
}); });
if (window.google) { if (window.maplibregl) {
// timeout needed so that the map is in the DOM // timeout needed so that the map is in the DOM
setTimeout(initMap); setTimeout(initMap);
} else if (window.googleCallback) {
(function interval() {
isLoaded() ? initMap() : setTimeout(interval, 100);
}());
} else { } else {
window.googleCallback = function() { Ox.getStylesheet([
delete window.googleCallback; Ox.PATH + 'UI/maplibre-gl/maplibre-gl.css',
initMap(); Ox.PATH + 'UI/maplibre-gl/maplibre-gl-geocoder.css'
}; ], () => {})
$.getScript( $.getScript([
document.location.protocol Ox.PATH + 'UI/maplibre-gl/maplibre-gl.js',
+ '//maps.google.com/maps/api/js?callback=googleCallback&sensor=false' Ox.PATH + 'UI/maplibre-gl/maplibre-gl-geocoder.min.js',
+ (Ox.Map.GoogleApiKey ? '&key=' + Ox.Map.GoogleApiKey : '') ], 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) { function addPlaceToMap(place) {
@ -535,11 +541,11 @@ Ox.Map = function(options, self) {
if (!place) { if (!place) {
var bounds = self.map.getBounds(), var bounds = self.map.getBounds(),
center = self.map.getCenter(), center = self.map.getCenter(),
southwest = new google.maps.LatLngBounds( southwest = new maplibregl.LngLatBounds(
bounds.getSouthWest(), center bounds._sw, center
).getCenter(), ).getCenter(),
northeast = new google.maps.LatLngBounds( northeast = new maplibregl.LngLatBounds(
center, bounds.getNorthEast() center, bounds._ne
).getCenter(), ).getCenter(),
place = new Ox.MapPlace({ place = new Ox.MapPlace({
alternativeNames: [], alternativeNames: [],
@ -550,14 +556,14 @@ Ox.Map = function(options, self) {
map: that, map: that,
name: '', name: '',
type: 'feature', type: 'feature',
south: southwest.lat(), south: southwest.lat,
west: southwest.lng(), west: southwest.lng,
north: northeast.lat(), north: northeast.lat,
east: northeast.lng() east: northeast.lng
}); });
} }
Ox.forEach(self.places, function(p, i) { Ox.forEach(self.places, function(p, i) {
if (place.bounds.equals(p.bounds)) { if (equalBonds(place.bounds, p.bounds)) {
place = p; place = p;
exists = true; exists = true;
return false; // break return false; // break
@ -589,17 +595,23 @@ Ox.Map = function(options, self) {
self.boundsChanged = true; self.boundsChanged = true;
} }
function toSpan(bounds) {
return {
lat: bounds._ne.lat - bounds._sw.lat,
lng: bounds._ne.lng - bounds._sw.lng,
}
}
function canContain(outerBounds, innerBounds) { function canContain(outerBounds, innerBounds) {
// checks if outerBounds _can_ contain innerBounds // checks if outerBounds _can_ contain innerBounds
var outerSpan = outerBounds.toSpan(), var outerSpan = toSpan(outerBounds),
innerSpan = innerBounds.toSpan(); innerSpan = toSpan(innerBounds);
return outerSpan.lat() > innerSpan.lat() && return outerSpan.lat > innerSpan.lat &&
outerSpan.lng() > innerSpan.lng(); outerSpan.lng > innerSpan.lng;
} }
function centerChanged() { function centerChanged() {
var tooltip = $('.OxMapMarkerTooltip'); that.tooltip.remove()
tooltip.length && Ox.$elements[$(tooltip[0]).data('oxid')].hide();
self.center = self.map.getCenter(); self.center = self.map.getCenter();
self.centerChanged = true; self.centerChanged = true;
} }
@ -611,7 +623,7 @@ Ox.Map = function(options, self) {
function clickMap(event) { function clickMap(event) {
var place = getSelectedPlace(); var place = getSelectedPlace();
if (self.options.clickable/* && !editing()*/) { if (self.options.clickable/* && !editing()*/) {
getPlaceByLatLng(event.latLng, self.map.getBounds(), function(place) { getPlaceByLatLng(event.lngLat, self.map.getBounds(), function(place) {
if (place) { if (place) {
addPlaceToMap(place); addPlaceToMap(place);
//selectPlace(place.id); //selectPlace(place.id);
@ -656,7 +668,7 @@ Ox.Map = function(options, self) {
function crossesDateline() { function crossesDateline() {
var bounds = self.map.getBounds(); var bounds = self.map.getBounds();
return bounds.getSouthWest().lng() > bounds.getNorthEast().lng(); return bounds._sw.lng > bounds._ne.lng;
} }
function editing() { function editing() {
@ -681,9 +693,9 @@ Ox.Map = function(options, self) {
// get initial map bounds // get initial map bounds
self.options.places({}, function(result) { self.options.places({}, function(result) {
var area = result.data.area; var area = result.data.area;
callback(new google.maps.LatLngBounds( callback(new maplibregl.LngLatBounds(
new google.maps.LatLng(area.south, area.west), new maplibregl.LngLat(area.west, area.south),
new google.maps.LatLng(area.north, area.east) new maplibregl.LngLat(area.east, area.north)
)); ));
}); });
} }
@ -704,15 +716,23 @@ Ox.Map = function(options, self) {
callback = point; callback = point;
point = self.map.getCenter(); point = self.map.getCenter();
} }
// fixme, why is getMaxZoom off by one?
let maxZoom = self.map.getMaxZoom()
setTimeout(() => {
callback(maxZoom)
})
/*
self.maxZoomService.getMaxZoomAtLatLng(point, function(data) { self.maxZoomService.getMaxZoomAtLatLng(point, function(data) {
callback(data.status == 'OK' ? data.zoom : null); callback(data.status == 'OK' ? data.zoom : null);
}); });
*/
} }
function getMetersPerPixel() { function getMetersPerPixel() {
// m/px = m/deg * deg/px // m/px = m/deg * deg/px
var degreesPerPixel = 360 / (self.tileSize * Math.pow(2, self.map.getZoom())); const degreesPerPixel = 360 / (self.tileSize * Math.pow(2, self.map.getZoom()));
return Ox.getMetersPerDegree(self.map.getCenter().lat()) * degreesPerPixel; const center = self.map.getCenter();
return Ox.getMetersPerDegree(center.lat) * degreesPerPixel;
} }
function getMinZoom() { function getMinZoom() {
@ -734,34 +754,29 @@ Ox.Map = function(options, self) {
: Ox.getObjectById(self.places, id); : 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 // gets the largest place at latlng that would fit in bounds
var callback = arguments.length == 3 ? callback : bounds, var callback = arguments.length == 3 ? callback : bounds,
bounds = arguments.length == 3 ? bounds : null; bounds = arguments.length == 3 ? bounds : null;
self.$loadingIcon && self.$loadingIcon.start(); self.$loadingIcon && self.$loadingIcon.start();
self.geocoder.geocode({ var results = await reverseGeocode(latlng);
latLng: latlng
}, function(results, status) {
self.$loadingIcon && self.$loadingIcon.stop(); self.$loadingIcon && self.$loadingIcon.stop();
if (status == google.maps.GeocoderStatus.OK) { if (results.features.length) {
if (bounds) { if (bounds) {
Ox.forEach(results.reverse(), function(result, i) { Ox.forEach(results.features, function(result, i) {
if ( if (
i == results.length - 1 || i == results.length - 1 ||
canContain(bounds, result.geometry.bounds || result.geometry.viewport) canContain(bounds, result.bounds)
) { ) {
callback(new Ox.MapPlace(parseGeodata(results[i]))); callback(new Ox.MapPlace(parseGeodata(result)));
return false; // break return false; // break
} }
}); });
} else { } else {
callback(new Ox.MapPlace(parseGeodata(results[0]))); callback(new Ox.MapPlace(parseGeodata(results.features[0])));
} }
} }
if ( if (!results.length) {
status == google.maps.GeocoderStatus.OK ||
status == google.maps.GeocoderStatus.ZERO_RESULTS
) {
triggerGeocodeEvent({ triggerGeocodeEvent({
latLng: latlng, latLng: latlng,
results: results results: results
@ -770,25 +785,19 @@ Ox.Map = function(options, self) {
Ox.Log('Map', 'geocode failed:', status); Ox.Log('Map', 'geocode failed:', status);
callback(null); callback(null);
} }
});
} }
function getPlaceByName(name, callback) { function getPlaceByName(name, callback) {
self.$loadingIcon && self.$loadingIcon.start(); self.$loadingIcon && self.$loadingIcon.start();
self.geocoder.geocode({ forwardGeocode({
address: name query: name
}, function(results, status) { }).then(function(results) {
self.$loadingIcon && self.$loadingIcon.stop(); self.$loadingIcon && self.$loadingIcon.stop();
if (status == google.maps.GeocoderStatus.OK) { if (results.features.length) {
callback(new Ox.MapPlace(parseGeodata(results[0]))); callback(new Ox.MapPlace(parseGeodata(results.features[0])));
}
if (
status == google.maps.GeocoderStatus.OK
&& status != google.maps.GeocoderStatus.ZERO_RESULTS
) {
triggerGeocodeEvent({ triggerGeocodeEvent({
address: name, address: name,
results: results results: results.features
}); });
} else { } else {
Ox.Log('Map', 'geocode failed:', status); Ox.Log('Map', 'geocode failed:', status);
@ -831,36 +840,167 @@ Ox.Map = function(options, self) {
: null; : 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() { function initMap() {
getMapBounds(function(mapBounds) { 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.elevationService = new google.maps.ElevationService();
self.geocoder = new google.maps.Geocoder(); //self.maxZoomService = new google.maps.MaxZoomService();
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; 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, center: self.center,
disableDefaultUI: true, style2: self.options.style,
disableDoubleClickZoom: true, style: {
mapTypeId: google.maps.MapTypeId[getMapType()], 'version': 8,
noClear: true, 'sources': {
scrollwheel: !self.options.zoomOnlyWhenFocused, 'raster-tiles': {
'type': 'raster',
'tiles': [
'https://mt0.google.com/vt/lyrs=s&x={x}&y={y}&z={z}',
'https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}',
'https://mt2.google.com/vt/lyrs=s&x={x}&y={y}&z={z}',
'https://mt3.google.com/vt/lyrs=s&x={x}&y={y}&z={z}',
],
'tileSize': 256,
'attribution':
'FIXME',
}
},
'layers': [
{
'id': 'simple-tiles',
'type': 'raster',
'source': 'raster-tiles',
'roundZoom': true,
'minzoom': 0,
'maxzoom': 22
}
]
},
//mapTypeId: google.maps.MapTypeId[getMapType()],
//noClear: true,
//scrollwheel: !self.options.zoomOnlyWhenFocused,
zoom: self.zoom 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, 'bounds_changed', boundsChanged);
google.maps.event.addListener(self.map, 'center_changed', centerChanged); google.maps.event.addListener(self.map, 'center_changed', centerChanged);
google.maps.event.addListener(self.map, 'click', clickMap); google.maps.event.addListener(self.map, 'click', clickMap);
google.maps.event.addListener(self.map, 'idle', mapChanged); google.maps.event.addListener(self.map, 'idle', mapChanged);
google.maps.event.addListener(self.map, 'zoom_changed', zoomChanged); google.maps.event.addListener(self.map, 'zoom_changed', zoomChanged);
google.maps.event.trigger(self.map, 'resize'); google.maps.event.trigger(self.map, 'resize');
*/
// needed to get mouse x/y coordinates on marker mouseover, // needed to get mouse x/y coordinates on marker mouseover,
// see http://code.google.com/p/gmaps-api-issues/issues/detail?id=2342 // see http://code.google.com/p/gmaps-api-issues/issues/detail?id=2342
/*
that.overlayView = new google.maps.OverlayView(); that.overlayView = new google.maps.OverlayView();
that.overlayView.setMap(self.map); that.overlayView.setMap(self.map);
that.overlayView.draw = function () { that.overlayView.draw = function () {
@ -870,7 +1010,9 @@ Ox.Map = function(options, self) {
} }
} }
that.overlayView.draw(); that.overlayView.draw();
*/
that.map.on('load', () => {
Ox.forEach(self.$controls, function($control) { Ox.forEach(self.$controls, function($control) {
$control.appendTo(self.$map); $control.appendTo(self.$map);
}); });
@ -891,6 +1033,7 @@ Ox.Map = function(options, self) {
self.map.setZoom(self.minZoom); self.map.setZoom(self.minZoom);
} else { } else {
self.map.fitBounds(mapBounds); self.map.fitBounds(mapBounds);
} }
} }
if (self.map.getZoom() < self.minZoom) { if (self.map.getZoom() < self.minZoom) {
@ -899,23 +1042,23 @@ Ox.Map = function(options, self) {
} }
updateFormElements(); updateFormElements();
that.resizeMap()
self.loaded = true; self.loaded = true;
self.boundsChanged = true;
that.triggerEvent('load'); that.triggerEvent('load');
})
}); });
} }
function isEmpty(bounds) { function isEmpty(bounds) {
// Google's bounds.isEmpty() is not reliable return bounds._sw.lat == bounds._ne.lat
var southWest = bounds.getSouthWest(), && bounds._sw.lng == bounds._ne.lng;
northEast = bounds.getNorthEast();
return southWest.lat() == northEast.lat()
&& southWest.lng() == northEast.lng();
} }
function isLoaded() { function isLoaded() {
return window.google && window.google.maps && window.google.maps.LatLng; return window.maplibregl && window.maplibregl.maps && window.maplibregl.LngLat;
} }
function mapChanged() { function mapChanged() {
@ -927,12 +1070,11 @@ Ox.Map = function(options, self) {
self.boundsChanged = false; self.boundsChanged = false;
return return
} }
var southWest = bounds.getSouthWest(), var south = bounds._sw.lat,
northEast = bounds.getNorthEast(), west = bounds._sw.lng,
south = southWest.lat(), north = bounds._ne.lat,
west = southWest.lng(), east = bounds._ne.lng;
north = northEast.lat(),
east = northEast.lng();
self.options.places({ self.options.places({
keys: self.placeKeys, keys: self.placeKeys,
query: { query: {
@ -1015,22 +1157,24 @@ Ox.Map = function(options, self) {
} }
function parseGeodata(data) { function parseGeodata(data) {
var bounds = data.geometry.bounds || data.geometry.viewport, console.log("parseGeodata", data)
northEast = bounds.getNorthEast(), // FIXME: data is geojson Feature with Polygon geometry now
southWest = bounds.getSouthWest(), var bounds = data.bounds,
northEast = bounds._ne,
southWest = bounds._sw,
place = { place = {
alternativeNames: [], alternativeNames: [],
components: data.address_components, components: data.address_components,
countryCode: getCountryCode(data.address_components), countryCode: getCountryCode(data.address_components),
east: northEast.lng(), east: northEast.lng,
editable: self.options.editable, editable: self.options.editable,
fullGeoname: getFullGeoname(data.address_components), fullGeoname: getFullGeoname(data.address_components),
id: '_' + Ox.encodeBase32(Ox.uid()), id: '_' + Ox.encodeBase32(Ox.uid()),
map: that, map: that,
north: northEast.lat(), north: northEast.lat,
south: southWest.lat(), south: southWest.lat,
type: getType(data.address_components[0].types), type: getType(data.address_components[0].types),
west: southWest.lng() west: southWest.lng
}; };
place.geoname = data.formatted_address || place.fullGeoname; place.geoname = data.formatted_address || place.fullGeoname;
place.name = (place.geoname || place.fullGeoname).split(', ')[0]; place.name = (place.geoname || place.fullGeoname).split(', ')[0];
@ -1336,10 +1480,12 @@ Ox.Map = function(options, self) {
// someone may want to cache google geocode data, so we fire an event. // someone may want to cache google geocode data, so we fire an event.
// google puts functions like lat or lng on the objects' prototypes, // google puts functions like lat or lng on the objects' prototypes,
// so we create properly named properties, for json encoding // so we create properly named properties, for json encoding
console.log(data)
/*
if (data.latLng) { if (data.latLng) {
data.latLng = { data.latLng = {
lat: data.latLng.lat(), lat: data.latLng.lat,
lng: data.latLng.lng() lng: data.latLng.lng
} }
} }
data.results.forEach(function(result) { data.results.forEach(function(result) {
@ -1347,23 +1493,24 @@ Ox.Map = function(options, self) {
if (result.geometry[key]) { if (result.geometry[key]) {
result.geometry[key] = { result.geometry[key] = {
northEast: { northEast: {
lat: result.geometry[key].getNorthEast().lat(), lat: result.geometry[key]._ne.lat,
lng: result.geometry[key].getNorthEast().lng() lng: result.geometry[key]._ne.lng
}, },
southWest: { southWest: {
lat: result.geometry[key].getSouthWest().lat(), lat: result.geometry[key]._sw.lat,
lng: result.geometry[key].getSouthWest().lng() lng: result.geometry[key]._sw.lng
} }
} }
} }
}); });
if (result.geometry.location) { if (result.geometry.location) {
result.geometry.location = { result.geometry.location = {
lat: result.geometry.location.lat(), lat: result.geometry.location.lat,
lng: result.geometry.location.lng() lng: result.geometry.location.lng
} }
} }
}); });
*/
that.triggerEvent('geocode', data); that.triggerEvent('geocode', data);
} }
@ -1402,7 +1549,7 @@ Ox.Map = function(options, self) {
} }
function zoomChanged() { function zoomChanged() {
var zoom = self.map.getZoom(); var zoom = parseInt(self.map.getZoom());
if (zoom < self.minZoom) { if (zoom < self.minZoom) {
self.map.setZoom(self.minZoom); self.map.setZoom(self.minZoom);
} else if (self.maxZoom && zoom > self.maxZoom) { } else if (self.maxZoom && zoom > self.maxZoom) {
@ -1443,8 +1590,7 @@ Ox.Map = function(options, self) {
lng <n> Longitude lng <n> Longitude
@*/ @*/
that.getCenter = function() { that.getCenter = function() {
var center = self.map.getCenter(); return self.map.getCenter();
return {lat: center.lat(), lng: center.lng()};
}; };
/*@ /*@
@ -1544,7 +1690,7 @@ Ox.Map = function(options, self) {
}); });
updateFormElements(); updateFormElements();
Ox.Log('Map', 'triggering google maps resize event, height', self.options.height) 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); // self.map.setCenter(center);
} }
return that; return that;
@ -1558,7 +1704,7 @@ Ox.Map = function(options, self) {
lng <n> Longitude lng <n> Longitude
@*/ @*/
that.setCenter = function(center) { 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; return that;
}; };

View file

@ -46,6 +46,15 @@ Ox.MapEditor = function(options, self) {
self.$list.size(); self.$list.size();
self.$map.resizeMap(); self.$map.resizeMap();
}, },
places: function() {
self.isAsync = Ox.isFunction(self.options.places);
self.$list.options({
items: Ox.clone(self.options.places)
})
self.$map.options({
places: self.options.places
})
},
selected: function() { selected: function() {
self.$list.options({selected: self.options.selected}); self.$list.options({selected: self.options.selected});
}, },

View file

@ -42,16 +42,32 @@ Ox.MapMarker = function(options) {
Ox.forEach(options, function(val, key) { Ox.forEach(options, function(val, key) {
that[key] = val; that[key] = val;
}); });
that.marker = new google.maps.Marker({ setColor()
setSize()
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 = element.style.height = that.size + 'px'
that.marker = new maplibregl.Marker({
raiseOnDrag: false, raiseOnDrag: false,
shape: {coords: [8, 8, 8], type: 'circle'} element: element,
//shape: {coords: [8, 8, 8], type: 'circle'},
//title: that.place.name, //title: that.place.name,
//zIndex: 1000 //zIndex: 1000
}); });
that.tooltip = new maplibregl.Popup({
closeButton: false,
closeOnClick: false,
className: 'tooltip'
});
that.tooltip.addClass
setOptions(); setOptions();
function click() { function click(event) {
event.preventDefault()
event.stopPropagation()
var key = that.map.getKey(), var key = that.map.getKey(),
place, bounds, southWest, northEast; place, bounds, southWest, northEast;
if (!that.place.selected) { if (!that.place.selected) {
@ -62,9 +78,9 @@ Ox.MapMarker = function(options) {
place = that.map.getSelectedPlace(); place = that.map.getSelectedPlace();
} }
if (place) { if (place) {
bounds = new google.maps.LatLngBounds( bounds = new maplibregl.LngLatBounds(
new google.maps.LatLng(place.south, place.west), new maplibregl.LngLatBounds(place.west, place.south),
new google.maps.LatLng(place.north, place.east) new maplibregl.LngLatBounds(place.east, place.north)
); );
bounds = bounds.union(that.place.bounds); bounds = bounds.union(that.place.bounds);
southWest = bounds.getSouthWest(); southWest = bounds.getSouthWest();
@ -79,10 +95,10 @@ Ox.MapMarker = function(options) {
map: that.map, map: that.map,
name: '', name: '',
type: 'feature', type: 'feature',
south: southWest.lat(), south: southWest.lat,
west: southWest.lng(), west: southWest.lng,
north: northEast.lat(), north: northEast.lat,
east: northEast.lng() east: northEast.lng
})); }));
} else { } else {
that.map.options({selected: that.place.id}); that.map.options({selected: that.place.id});
@ -107,11 +123,11 @@ Ox.MapMarker = function(options) {
function drag(e) { function drag(e) {
var northSouth = (that.place.north - that.place.south) / 2, var northSouth = (that.place.north - that.place.south) / 2,
lat = Ox.limit( lat = Ox.limit(
e.latLng.lat(), e.lngLat.lat,
Ox.MIN_LATITUDE + northSouth, Ox.MIN_LATITUDE + northSouth,
Ox.MAX_LATITUDE - northSouth Ox.MAX_LATITUDE - northSouth
), ),
lng = e.latLng.lng(), lng = e.lngLat.lng,
span = Math.min( span = Math.min(
that.place.sizeEastWest * Ox.getDegreesPerMeter(lat) / 2, 179.99999999 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); Ox.Log('Map', 'west', that.place.west, 'east', that.place.east, 'span', span);
that.place.update(); that.place.update();
that.marker.setOptions({ that.marker.setLngLat(that.place.center)
position: that.place.center
});
that.place.rectangle.update(); that.place.rectangle.update();
} }
@ -181,35 +195,30 @@ Ox.MapMarker = function(options) {
c.context.strokeStyle = 'rgb(' + border.join(', ') + ')'; c.context.strokeStyle = 'rgb(' + border.join(', ') + ')';
c.context.arc(r, r, r - 1, 0, 360); c.context.arc(r, r, r - 1, 0, 360);
c.context.stroke(); c.context.stroke();
callback(new google.maps.MarkerImage( callback(new maplibregl.MarkerImage(
c.canvas.toDataURL(), c.canvas.toDataURL(),
new google.maps.Size(options.size, options.size), new maplibregl.Size(options.size, options.size),
new google.maps.Point(0, 0), new maplibregl.Point(0, 0),
new google.maps.Point(r, r) new maplibregl.Point(r, r)
)); ));
} }
} }
function mouseover(e) { function mouseover(e) {
var offset = that.map.offset(), that.tooltip.setLngLat(that.place.center).setHTML(
xy = that.map.overlayView.getProjection() '<div style="display: flex; gap: 8px; margin: 2px 2px"><img src="' + Ox.getFlagByGeoname(that.place.geoname, 16)
.fromLatLngToContainerPixel(e.latLng); + '" style="border-radius: 4px;margin: auto"/>'
that.tooltip.show( + '<div style="font-size: 9px;">'
offset.left + Math.round(xy.x) - 4, + that.map.options('markerTooltip')(that.place) + '</div></div>'
offset.top + Math.round(xy.y) + 20 ).addTo(that.map.map);
);
} }
function mouseout() { function mouseout() {
that.tooltip.hide(); that.tooltip.remove();
} }
function setOptions() { function setColor() {
// workaround to prevent marker from appearing twice var color = that.map.options('markerColor');
// 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');
//Ox.Log('Map', 'setOptions, that.map: ', that.map) //Ox.Log('Map', 'setOptions, that.map: ', that.map)
if (color == 'auto') { if (color == 'auto') {
that.color = typeColor[that.place.type] || typeColor['mapPlaceFeatureColor']; that.color = typeColor[that.place.type] || typeColor['mapPlaceFeatureColor'];
@ -218,6 +227,10 @@ Ox.MapMarker = function(options) {
} else { } else {
that.color = color(that.place); that.color = color(that.place);
} }
}
function setSize() {
var size = that.map.options('markerSize');
if (size == 'auto') { if (size == 'auto') {
that.size = 8; that.size = 8;
Ox.forEach(areaSize, function(size, area) { Ox.forEach(areaSize, function(size, area) {
@ -232,50 +245,57 @@ Ox.MapMarker = function(options) {
} else { } else {
that.size = size(that.place); that.size = size(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()
setSize()
/* fixme, some of those can be set some not
that.marker.setOptions({ that.marker.setOptions({
// fixme: cursor remains pointer // fixme: cursor remains pointer
cursor: that.place.editing ? 'move' : 'pointer', cursor: that.place.editing ? 'move' : 'pointer',
draggable: that.place.editing, draggable: that.place.editing,
icon: Ox.MapMarkerImage({ element: Ox.MapMarkerImage({
color: that.color, color: that.color,
mode: that.place.editing ? 'editing' : mode: that.place.editing ? 'editing' :
that.place.selected ? 'selected' : 'normal', that.place.selected ? 'selected' : 'normal',
size: that.size, size: that.size,
type: that.place.id[0] == '_' ? 'result' : 'place' 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._element.height = that.marker._element.width = that.size + 'px'
that.marker.setDraggable(that.place.editing);
that.marker.setLngLat(that.place.center);
if (fix) { if (fix) {
that.marker.setVisible(false); that.marker.setVisible(false);
setTimeout(function() { setTimeout(function() {
that.marker.setVisible(true); that.marker.setVisible(true);
}, 0); }, 0);
} }
setTooltip();
} }
function setTooltip() {
that.tooltip && that.tooltip.remove();
that.tooltip = Ox.Tooltip({
title: '<img src="'
+ Ox.getFlagByGeoname(that.place.geoname, 16)
+ '" style="float: left; width: 16px; height: 16px; margin: 1px 0 1px -1px; border-radius: 4px"/>'
+ '<div style="float: left; margin: 4px -1px 0 4px; font-size: 9px;">'
+ that.map.options('markerTooltip')(that.place) + '</div>'
})
.addClass('OxMapMarkerTooltip');
}
/*@ /*@
add <f> add to map add <f> add to map
() -> <f> add to map, returns MapMarker () -> <f> add to map, returns MapMarker
@*/ @*/
that.add = function() { that.add = function() {
that.marker.setMap(that.map.map); that.marker.addTo(that.map.map);
google.maps.event.addListener(that.marker, 'click', click); const element = that.marker.getElement()
google.maps.event.addListener(that.marker, 'dblclick', dblclick); if(element) {
google.maps.event.addListener(that.marker, 'mouseover', mouseover); element.addEventListener('click', click)
google.maps.event.addListener(that.marker, 'mouseout', mouseout); element.addEventListener('dblclick', dblclick)
element.addEventListener('mouseover', mouseover)
element.addEventListener('mouseout', mouseout)
}
return that; return that;
}; };
@ -285,9 +305,9 @@ Ox.MapMarker = function(options) {
@*/ @*/
that.edit = function() { that.edit = function() {
setOptions(); setOptions();
google.maps.event.addListener(that.marker, 'dragstart', dragstart); that.marker.on('dragstart', dragstart);
google.maps.event.addListener(that.marker, 'drag', drag); that.marker.on('drag', drag);
google.maps.event.addListener(that.marker, 'dragend', dragend); that.marker.on('dragend', dragend);
return that; return that;
}; };
@ -296,8 +316,12 @@ Ox.MapMarker = function(options) {
() -> <f> remove marker from map, returns MapMarker () -> <f> remove marker from map, returns MapMarker
@*/ @*/
that.remove = function() { that.remove = function() {
that.marker.setMap(null); that.marker.remove();
google.maps.event.clearListeners(that.marker); //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; return that;
}; };
@ -306,9 +330,9 @@ Ox.MapMarker = function(options) {
() -> <f> clear edit listeners, returns MapMarker () -> <f> clear edit listeners, returns MapMarker
@*/ @*/
that.submit = function() { that.submit = function() {
google.maps.event.clearListeners(that.marker, 'dragstart'); that.marker.off('dragstart');
google.maps.event.clearListeners(that.marker, 'drag'); that.marker.off('drag');
google.maps.event.clearListeners(that.marker, 'dragend'); that.marker.off('dragend');
return that; return that;
} }

View file

@ -50,12 +50,10 @@ Ox.MapMarkerImage = (function() {
c.context.strokeStyle = 'rgba(' + border.join(', ') + ')'; c.context.strokeStyle = 'rgba(' + border.join(', ') + ')';
c.context.arc(r, r, r - 1, 0, 360); c.context.arc(r, r, r - 1, 0, 360);
c.context.stroke(); c.context.stroke();
cache[index] = new google.maps.MarkerImage( cache[index] = document.createElement('img')
c.canvas.toDataURL(), cache[index].src = c.canvas.toDataURL()
new google.maps.Size(options.size, options.size), cache[index].width = options.size
new google.maps.Point(0, 0), cache[index].height = options.size
new google.maps.Point(r, r)
);
} }
return cache[index]; return cache[index];

View file

@ -44,21 +44,24 @@ Ox.MapPlace = function(options) {
update(); update();
function update(updateMarker) { function update(updateMarker) {
if (that.west > that.east) {
that.east += 360;
}
that.points = { that.points = {
ne: new google.maps.LatLng(that.north, that.east), ne: new maplibregl.LngLat(that.east, that.north),
sw: new google.maps.LatLng(that.south, that.west) 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.center = that.bounds.getCenter();
that.lat = that.center.lat(); that.lat = that.center.lat;
that.lng = that.center.lng(); that.lng = that.center.lng;
Ox.extend(that.points, { Ox.extend(that.points, {
e: new google.maps.LatLng(that.lat, that.east), e: new maplibregl.LngLat(that.east, that.lat),
s: new google.maps.LatLng(that.south, that.lng), s: new maplibregl.LngLat(that.lng, that.south),
se: new google.maps.LatLng(that.south, that.east), se: new maplibregl.LngLat(that.east, that.south),
n: new google.maps.LatLng(that.north, that.lng), n: new maplibregl.LngLat(that.lng, that.north),
nw: new google.maps.LatLng(that.north, that.west), nw: new maplibregl.LngLat(that.west, that.north),
w: new google.maps.LatLng(that.lat, that.west) w: new maplibregl.LngLat(that.west, that.lat)
}); });
// fixme: use bounds.toSpan() // fixme: use bounds.toSpan()
that.sizeNorthSouth = (that.north - that.south) that.sizeNorthSouth = (that.north - that.south)
@ -79,8 +82,9 @@ Ox.MapPlace = function(options) {
place: that place: that
}); });
} else if (updateMarker) { } else if (updateMarker) {
that.marker.update(); console.log("fixme update marker")
that.rectangle.update(); //that.marker.update();
//that.rectangle.update();
} }
} }

View file

@ -1,5 +1,178 @@
'use strict'; '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
}
};
var sourceId = `${this.id}-rectangles`
this.source = this.map.getSource(sourceId)
if (!this.source) {
this.map.addSource(sourceId, {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: []
}
});
this.source = this.map.getSource(sourceId)
// Add fill layer
var layerId = `${this.id}-fill`
if (!this.map.getLayer(layerId)) {
this.map.addLayer({
id: layerId,
type: 'fill',
source: sourceId,
paint: {
'fill-color': '#088',
'fill-opacity': 0.3
}
});
}
// Add outline layer
var layerId = `${this.id}-outline`
if (!this.map.getLayer(layerId)) {
this.map.addLayer({
id: layerId,
type: 'line',
source: sourceId,
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 => {
console.log('click', e)
if (this.onclick) {
e.preventDefault()
//e.stopPropagation()
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 <f> MapRectangle Object Ox.MapRectangle <f> MapRectangle Object
(options) -> <o> MapRectangle Object (options) -> <o> MapRectangle Object
@ -25,10 +198,18 @@ Ox.MapRectangle = function(options) {
/*@ /*@
rectangle <f> google.maps.Rectangle rectangle <f> google.maps.Rectangle
@*/ @*/
/*
that.rectangle = new google.maps.Rectangle({ that.rectangle = new google.maps.Rectangle({
clickable: true, clickable: true,
bounds: that.place.bounds bounds: that.place.bounds
}); });
*/
that.rectangle = new MapLibreRectangle({
bounds: that.place.bounds,
});
that.rectangle.addTo(that.map.map);
that.rectangle.onclick = click
/*@ /*@
markers <a> array of markers markers <a> array of markers
@*/ @*/
@ -42,7 +223,8 @@ Ox.MapRectangle = function(options) {
setOptions(); setOptions();
function click() { function click(e) {
console.log('rectangle click', e)
if ( if (
that.map.options('editable') that.map.options('editable')
&& that.place.editable && that.place.editable
@ -64,6 +246,7 @@ Ox.MapRectangle = function(options) {
? 'mapPlaceEditingBorder' ? 'mapPlaceEditingBorder'
: 'mapPlaceSelectedBorder' : 'mapPlaceSelectedBorder'
]); ]);
/*
that.rectangle.setOptions({ that.rectangle.setOptions({
bounds: that.place.bounds, bounds: that.place.bounds,
fillColor: color, fillColor: color,
@ -72,14 +255,24 @@ Ox.MapRectangle = function(options) {
strokeOpacity: that.place.id[0] == '_' ? 0.5 : 1, strokeOpacity: that.place.id[0] == '_' ? 0.5 : 1,
strokeWeight: 2 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 <f> add add <f> add
@*/ @*/
that.add = function() { that.add = function() {
that.rectangle.setMap(that.map.map); that.rectangle.add()
google.maps.event.addListener(that.rectangle, 'click', click);
return that; return that;
}; };
@ -99,8 +292,7 @@ Ox.MapRectangle = function(options) {
remove <f> remove remove <f> remove
@*/ @*/
that.remove = function() { that.remove = function() {
that.rectangle.setMap(null); that.rectangle.remove();
google.maps.event.clearListeners(that.rectangle);
return that; return that;
} }

View file

@ -23,6 +23,7 @@ Ox.MapRectangleMarker = function(options) {
that[key] = val; that[key] = val;
}); });
/*
that.markerImage = new google.maps.MarkerImage that.markerImage = new google.maps.MarkerImage
that.marker = new google.maps.Marker({ that.marker = new google.maps.Marker({
cursor: that.position + '-resize', cursor: that.position + '-resize',
@ -35,20 +36,31 @@ Ox.MapRectangleMarker = function(options) {
position: that.place.points[that.position], position: that.place.points[that.position],
raiseOnDrag: false 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'
}),
});
that.marker.setLngLat(that.place.points[that.position])
function dragstart(e) { function dragstart(e) {
Ox.$body.addClass('OxDragging'); Ox.$body.addClass('OxDragging');
that.drag = { that.drag = {
lat: e.latLng.lat(), lat: e.lngLat.lat,
lng: e.latLng.lng() lng: e.lngLat.lng
}; };
} }
function drag(e) { function drag(e) {
// fixme: implement shift+drag (center stays the same) // fixme: implement shift+drag (center stays the same)
Ox.Log('Map', e.pixel.x, e.pixel.y) Ox.Log('Map', e.pixel.x, e.pixel.y)
var lat = Ox.limit(e.latLng.lat(), Ox.MIN_LATITUDE, Ox.MAX_LATITUDE), var lat = Ox.limit(e.lngLat.lat, Ox.MIN_LATITUDE, Ox.MAX_LATITUDE),
lng = e.latLng.lng(); lng = e.lngLat.lng;
that.drag = { that.drag = {
lat: lat, lat: lat,
lng: lng lng: lng
@ -90,32 +102,37 @@ Ox.MapRectangleMarker = function(options) {
add <f> add add <f> add
@*/ @*/
that.add = function() { that.add = function() {
that.marker.setMap(that.map.map); that.marker.addTo(that.map.map);
google.maps.event.addListener(that.marker, 'dragstart', dragstart); that.marker.on('dragstart', dragstart);
google.maps.event.addListener(that.marker, 'drag', drag); that.marker.on('drag', drag);
google.maps.event.addListener(that.marker, 'dragend', dragend); that.marker.on('dragend', dragend);
}; };
/*@ /*@
remove <f> remove remove <f> remove
@*/ @*/
that.remove = function() { that.remove = function() {
that.marker.setMap(null); that.marker.remove();
google.maps.event.clearListeners(that.marker); console.log("remove marker, fix events")
//google.maps.event.clearListeners(that.marker);
}; };
/*@ /*@
update <f> update update <f> update
@*/ @*/
that.update = function() { that.update = function() {
that.marker.setOptions({ var marker = new maplibregl.Marker({
icon: Ox.MapMarkerImage({ cursor: that.position + '-resize',
draggable: true,
element: Ox.MapMarkerImage({
mode: 'editing', mode: 'editing',
rectangle: true, rectangle: true,
type: that.place.id[0] == '_' ? 'result' : 'place' type: that.place.id[0] == '_' ? 'result' : 'place'
}), }),
position: that.place.points[that.position]
}); });
marker.setLngLat(that.place.points[that.position])
that.marker.remove()
that.marker = marker
}; };
return that; return that;

View file

@ -0,0 +1,284 @@
/* Basics */
.maplibregl-ctrl-geocoder,
.maplibregl-ctrl-geocoder *,
.maplibregl-ctrl-geocoder *:after,
.maplibregl-ctrl-geocoder *:before {
box-sizing: border-box;
}
.maplibregl-ctrl-geocoder {
font-size: 18px;
line-height: 24px;
font-family: "Open Sans", "Helvetica Neue", Arial, Helvetica, sans-serif;
position: relative;
background-color: #fff;
width: 100%;
min-width: 240px;
z-index: 1;
border-radius: 4px;
transition: width 0.25s, min-width 0.25s;
}
.maplibregl-ctrl-geocoder--input {
font: inherit;
width: 100%;
border: 0;
background-color: transparent;
margin: 0;
height: 50px;
color: #404040; /* fallback */
color: rgba(0, 0, 0, 0.75);
padding: 6px 45px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.maplibregl-ctrl-geocoder--input::-ms-clear {
display: none; /* hide input clear button in IE */
}
.maplibregl-ctrl-geocoder--input:focus {
color: #404040; /* fallback */
color: rgba(0, 0, 0, 0.75);
outline: 0;
box-shadow: none;
outline: thin dotted;
}
.maplibregl-ctrl-geocoder .maplibregl-ctrl-geocoder--pin-right > * {
z-index: 2;
position: absolute;
right: 8px;
top: 7px;
display: none;
}
.maplibregl-ctrl-geocoder,
.maplibregl-ctrl-geocoder .suggestions {
box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.1);
}
/* Collapsed */
.maplibregl-ctrl-geocoder.maplibregl-ctrl-geocoder--collapsed {
width: 50px;
min-width: 50px;
transition: width 0.25s, min-width 0.25s;
}
/* Suggestions */
.maplibregl-ctrl-geocoder .suggestions {
background-color: #fff;
border-radius: 4px;
left: 0;
list-style: none;
margin: 0;
padding: 0;
position: absolute;
width: 100%;
top: 110%; /* fallback */
top: calc(100% + 6px);
z-index: 1000;
overflow: hidden;
font-size: 15px;
}
.maplibregl-ctrl-bottom-left .suggestions,
.maplibregl-ctrl-bottom-right .suggestions {
top: auto;
bottom: 100%;
}
.maplibregl-ctrl-geocoder .suggestions > li > a {
cursor: default;
display: block;
padding: 6px 12px;
color: #404040;
}
.maplibregl-ctrl-geocoder .suggestions > .active > a,
.maplibregl-ctrl-geocoder .suggestions > li > a:hover {
color: #404040;
background-color: #f3f3f3;
text-decoration: none;
cursor: pointer;
}
.maplibregl-ctrl-geocoder--suggestion {
display: flex;
flex-direction: row;
align-items: center;
}
.maplibre-ctrl-geocoder--suggestion-icon {
min-width: 30px;
min-height: 24px;
max-width: 30px;
max-height: 24px;
padding-right: 12px;
}
.maplibregl-ctrl-geocoder--suggestion-info {
display: flex;
flex-direction: column;
}
.maplibregl-ctrl-geocoder--suggestion-match {
font-weight: bold;
}
.maplibregl-ctrl-geocoder--suggestion-title,
.maplibregl-ctrl-geocoder--suggestion-address {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.maplibregl-ctrl-geocoder--result {
display: flex;
flex-direction: row;
align-items: center;
}
.maplibre-ctrl-geocoder--result-icon {
min-width: 30px;
min-height: 24px;
max-width: 30px;
max-height: 24px;
padding-right: 12px;
}
.maplibregl-ctrl-geocoder--result-title {
font-weight: bold;
}
.maplibregl-ctrl-geocoder--result-title,
.maplibregl-ctrl-geocoder--result-address {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
/* Icons */
.maplibregl-ctrl-geocoder--icon {
display: inline-block;
vertical-align: middle;
speak: none;
fill: #757575;
top: 15px;
}
.maplibregl-ctrl-geocoder--icon-search {
position: absolute;
top: 13px;
left: 12px;
width: 23px;
height: 23px;
}
.maplibregl-ctrl-geocoder--button {
padding: 0;
margin: 0;
border: none;
cursor: pointer;
background: #fff;
line-height: 1;
}
.maplibregl-ctrl-geocoder--icon-close {
width: 20px;
height: 20px;
margin-top: 8px;
margin-right: 3px;
}
.maplibregl-ctrl-geocoder--button:hover .maplibregl-ctrl-geocoder--icon-close {
fill: #909090;
}
.maplibregl-ctrl-geocoder--icon-loading {
width: 26px;
height: 26px;
margin-top: 5px;
margin-right: 0px;
-moz-animation: rotate 0.8s infinite cubic-bezier(0.45, 0.05, 0.55, 0.95);
-webkit-animation: rotate 0.8s infinite cubic-bezier(0.45, 0.05, 0.55, 0.95);
animation: rotate 0.8s infinite cubic-bezier(0.45, 0.05, 0.55, 0.95);
}
/* Animation */
@-webkit-keyframes rotate {
from {
-webkit-transform: rotate(0);
transform: rotate(0);
}
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes rotate {
from {
-webkit-transform: rotate(0);
transform: rotate(0);
}
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
/* Media queries*/
@media screen and (min-width: 640px) {
.maplibregl-ctrl-geocoder.maplibregl-ctrl-geocoder--collapsed {
width: 36px;
min-width: 36px;
}
.maplibregl-ctrl-geocoder {
width: 33.3333%;
font-size: 15px;
line-height: 20px;
max-width: 360px;
}
.maplibregl-ctrl-geocoder .suggestions {
font-size: 13px;
}
.maplibregl-ctrl-geocoder--icon {
top: 8px;
}
.maplibregl-ctrl-geocoder--icon-close {
width: 16px;
height: 16px;
margin-top: 3px;
margin-right: 0;
}
.maplibregl-ctrl-geocoder--icon-search {
left: 7px;
width: 20px;
height: 20px;
}
.maplibregl-ctrl-geocoder--input {
height: 36px;
padding: 6px 35px;
}
.maplibregl-ctrl-geocoder--icon-loading {
width: 26px;
height: 26px;
margin-top: -2px;
margin-right: -5px;
}
.maplibre-gl-geocoder--error {
color: #909090;
padding: 6px 12px;
font-size: 16px;
text-align: center;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -92,13 +92,15 @@ def build_oxjs(downloads=False, geo=False):
# copy & link # copy & link
ui_files = {'dev': [], 'min': []} ui_files = {'dev': [], 'min': []}
css_files = ['maplibre-gl.css', 'maplibre-gl-geocoder.css']
for path, dirnames, filenames in os.walk(source_path): for path, dirnames, filenames in os.walk(source_path):
for filename in filenames: for filename in filenames:
if '_' not in path and filename[0] not in '._' \ if '_' not in path and filename[0] not in '._' \
and not filename.endswith('~') \ and not filename.endswith('~') \
and not filename.endswith('.css') \
and '/UI/svg' not in path \ and '/UI/svg' not in path \
and (geo or '/Geo/' not in path): and (geo or '/Geo/' not in path):
if filename.endswith('.css') and filename not in css_files:
continue
# write copies in min path # write copies in min path
source = os.path.join(path, filename) source = os.path.join(path, filename)
is_jquery = re.search(r'^jquery-[\d\.]+\.js$', filename) is_jquery = re.search(r'^jquery-[\d\.]+\.js$', filename)