Extending leaflet SVG renderer fails to show vector layers - javascript

I am working on modifying the Leaflet SVG renderer for some cosmetic changes, but I have not been able to get even the most basic extension to work (even without any custom code aside from changing things like create() to L.DomUtil.create()).
here is my most simple example setup:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""/>
<script src="https://unpkg.com/leaflet#1.6.0/dist/leaflet.js" integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" crossorigin=""></script>
<style>
body{
padding: 0;
margin: 0;
}
#mapid{
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<div id="mapid" style=""></div>
<script>
L.SVG.test = L.SVG.extend({
_initPath: function(layer){
const path = layer._path = L.DomUtil.create('path');
// #namespace Path
// #option className: String = null
// Custom class name set on an element. Only for SVG renderer.
if(layer.options.className){
path.classList.add(...L.Util.splitWords(layer.options.className));
}
if(layer.options.interactive){
path.classList.add('leaflet-interactive');
}
this._updateStyle(layer);
this._layers[L.Util.stamp(layer)] = layer;
},
_updateStyle: function(layer){
const path = layer._path,
options = layer.options;
if(!path){
return;
}
if(options.stroke){
path.setAttribute('stroke', options.color);
path.setAttribute('stroke-opacity', options.opacity);
path.setAttribute('stroke-width', options.weight);
path.setAttribute('stroke-linecap', options.lineCap);
path.setAttribute('stroke-linejoin', options.lineJoin);
if(options.dashArray){
path.setAttribute('stroke-dasharray', options.dashArray);
} else {
path.removeAttribute('stroke-dasharray');
}
if(options.dashOffset){
path.setAttribute('stroke-dashoffset', options.dashOffset);
} else {
path.removeAttribute('stroke-dashoffset');
}
} else {
path.setAttribute('stroke', 'none');
}
if(options.fill){
path.setAttribute('fill', options.fillColor || options.color);
path.setAttribute('fill-opacity', options.fillOpacity);
path.setAttribute('fill-rule', options.fillRule || 'evenodd');
} else {
path.setAttribute('fill', 'none');
}
}
});
var testRenderer = new L.SVG.test();
var map = L.map('mapid', {preferCanvas: false, renderer: testRenderer}).setView([51.505, -0.09], 13);
var tiles = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap'
}).addTo(map);
L.marker([51.5, -0.09]).addTo(map)
.bindPopup("<b>Hello world!</b><br />I am a popup.");
L.polygon([
[51.509, -0.08],
[51.503, -0.06],
[51.51, -0.047]
], {fillOpacity: 0.5}).addTo(map).bindPopup("I am a polygon.");
L.circle([51.508, -0.11], 500, {fillOpacity: 0.5}).addTo(map).bindPopup("I am a circle.");
</script>
</body>
</html>
It does not throw any errors in the console and I really don't know where to go from here.
Inspecting the dom shows that the layers exist in the svg tag and that they have coordinates, but the g element does not seem to have any height.
what am I doing wrong?

Related

Leaflet circle loop remove past and retain recent iterations

Good day!
I am trying to play around with animating a pulsating radius based on leaflet L.cicle and have managed to limit the radius size.
However, I am having a problem with several iterations of the radius are not removed from the map, resulting in a large number of circles.
I would appreciate some advice on how to remove the previous iterations of the circle and keep the most recent circle generated.
<!DOCTYPE html>
<html>
<head>
<title>Map</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" type="image/x-icon" href="docs/images/favicon.ico" />
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/>
<script src="https://unpkg.com/leaflet#1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
<style>
html, body {
height: 100%;
margin: 0;
}
#map {
width: 600px;
height: 400px;
}
</style>
<style>body { padding: 0; margin: 0; } #map { height: 100%; width: 100vw; }</style>
</head>
<body>
<div></div>
<div id='map'></div>
<script>
var map = L.map('map').setView([1.3400776203517657, 103.88408580637439],6);
L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=ACCESS_TOKEN', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, ' +
'Imagery © Mapbox',
id: 'mapbox/streets-v11',
tileSize: 512,
zoomOffset: -1
}).addTo(map);
function radiuspulse() {
const secs = 0.1 * 60;
let radiuschange = 0;
setInterval(function() {
if (radiuschange < 50000) {
radiuschange += 500;
const circle = L.circle([1.3400776203517657, 103.88408580637439], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.1,
weight:0,
id: 'abc123',
radius: radiuschange
}).addTo(map);
function clearcontent() {
document.getElementsByTagName("g").innerHTML = "";
};
}
else {
// clearInterval();
let radiuschange = 0;
radiuspulse();
}
}, secs);
}
radiuspulse()
</script>
</body>
</html>
Don't create always a new Circle, change only the radius with setRadius():
Also you can remove the circle with circle.removeFrom(map)
var circle = L.circle([1.3400776203517657, 103.88408580637439], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.1,
weight:0,
id: 'abc123',
radius: 500
}).addTo(map);
var interval = null;
function radiuspulse() {
const secs = 0.1 * 60;
let radiuschange = circle.getRadius();
interval = setInterval(function() {
if (radiuschange < 50000) {
radiuschange += 500;
circle.setRadius(radiuschange);
}
else {
clearInterval(interval);
circle.setRadius(0);
radiuspulse();
}
}, secs);
}
function clearcontent() {
circle.removeFrom(map);
}

Here maps api: Behavior not reacting in Microsoft Web Browser ActiveX

I tried example in Here maps about draggable Marker:
Draggable Marker | Here
On first try map didn't show in AX2009 Microsoft Web Browser ActiveX. I switched from vector to raster and added engineType: H.map.render.RenderEngine.EngineType.P2The marker in default web browser like IE11, Chrome, edge is working fine, I can drag marker. But in ActiveX on AX2009 the marker isnt dragging, but i can see map istelf like in default web browser. Any ideas what should i add to code? Any help appreciated.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<script src="https://js.api.here.com/v3/3.1/mapsjs-core.js"></script>
<script src="https://js.api.here.com/v3/3.1/mapsjs-core-legacy.js"></script>
<script src="https://js.api.here.com/v3/3.1/mapsjs-mapevents.js"></script>
<script src="https://js.api.here.com/v3/3.1/mapsjs-service.js"></script>
<script src="https://js.api.here.com/v3/3.1/mapsjs-service-legacy.js"></script>
<script src="https://js.api.here.com/v3/3.1/mapsjs-ui.js"></script>
<link
rel="stylesheet"
type="text/css"
href="https://js.api.here.com/v3/3.1/mapsjs-ui.css"
/>
<title>Simple Map</title>
<style>
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#map {
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
var point = {lat:56.983849, lng:24.237360};
var apikey = "xxx",
platform = new H.service.Platform({
apikey: apikey,
}),
pixelRatio = window.devicePixelRatio || 1,
ppi,
defaultLayers,
mapElement = document.getElementById("map"),
tileSize,
map,
behavior,
ui;
if (pixelRatio > 1) {
ppi = 250;
}
defaultLayers = platform.createDefaultLayers({
ppi: ppi,
});
map = new H.Map(mapElement, defaultLayers.raster.normal.map, {
zoom: 14,
center: point,
renderBaseBackground: { lower: 2, higher: 2 },
pixelRatio: pixelRatio,
engineType: H.map.render.RenderEngine.EngineType.P2D,
});
window.addEventListener("resize", function () {
map.getViewPort().resize();
});
function addDraggableMarker(map, behavior){
var marker = new H.map.Marker(point, {
// mark the object as volatile for the smooth dragging
volatility: true
});
// Ensure that the marker can receive drag events
marker.draggable = true;
map.addObject(marker);
// disable the default draggability of the underlying map
// and calculate the offset between mouse and target's position
// when starting to drag a marker object:
map.addEventListener('dragstart', function(ev) {
var target = ev.target,
pointer = ev.currentPointer;
if (target instanceof H.map.Marker) {
var targetPosition = map.geoToScreen(target.getGeometry());
target['offset'] = new H.math.Point(pointer.viewportX - targetPosition.x, pointer.viewportY - targetPosition.y);
behavior.disable();
}
}, false);
// re-enable the default draggability of the underlying map
// when dragging has completed
map.addEventListener('dragend', function(ev) {
var target = ev.target;
if (target instanceof H.map.Marker) {
behavior.enable();
}
}, false);
// Listen to the drag event and move the position of the marker
// as necessary
map.addEventListener('drag', function(ev) {
var target = ev.target,
pointer = ev.currentPointer;
if (target instanceof H.map.Marker) {
target.setGeometry(map.screenToGeo(pointer.viewportX - target['offset'].x, pointer.viewportY - target['offset'].y));
}
}, false);
}
behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
ui = H.ui.UI.createDefault(map, defaultLayers);
addDraggableMarker(map, behavior);
</script>
</body>
</html>

Disable redrawing canvas after screen resize and mobile rotation in PaperJS

The use case is for a Christmas "scratch" card, where the user needs to swipe on the image to reveal the content. When the window is resized or the phone is rotated, the canvas is redrawn. My current code is as follows:
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Division Raster</title>
<script type="text/javascript" src="wp-content/themes/generatepress_child/paper-full.min.js"></script>
<script type="text/paperscript" canvas="canvas">
// Based on 'JPEG Raster' by Jonathan Puckey:
// http://www.flickr.com/photos/puckey/3179779686/in/photostream/
// Create a raster item using the image with id='mona'
var raster = new Raster('mona');
// Make the raster invisible:
raster.visible = true;
raster.position = view.center;
var lastPos = view.center;
function moveHandler(event) {
if (lastPos.getDistance(event.point) < 1)
return;
lastPos = event.point;
var size = this.bounds.size.clone();
var isLandscape = size.width > size.height;
// If the path is in landscape orientation, we're going to
// split the path horizontally, otherwise vertically:
size /= isLandscape ? [2, 1] : [1, 2];
if (size.ceil().width > 10) {
var path = new Path.Rectangle({
point: this.bounds.topLeft.floor(),
size: size.ceil(),
onMouseMove: moveHandler
});
path.fillColor = raster.getAverageColor(path);
var path = new Path.Rectangle({
point: isLandscape
? this.bounds.topCenter.ceil()
: this.bounds.leftCenter.ceil(),
size: size.floor(),
onMouseMove: moveHandler
});
path.fillColor = raster.getAverageColor(path);
}
this.remove();
}
function onResize(event) {
project.activeLayer.removeChildren();
// Transform the raster so that it fills the bounding rectangle
// of the view:
raster.fitBounds(view.bounds, true);
// Create a path that fills the view, and fill it with
// the average color of the raster:
new Path.Rectangle({
rectangle: view.bounds,
fillColor: raster.getAverageColor(view.bounds),
onMouseMove: moveHandler
});
}
</script>
<style type="text/css" id="wp-custom-css">
#canvas{
background: center center url(/web.jpg);
width:100%;
height:100vh;
position: absolute;
top:0;
bottom: 0;
left: 0;
right: 0;
margin: 0 auto;
}
#cc{
max-width:2560px;
position: absolute;
top:0;
bottom: 0;
left: 0;
right: 0;
margin: 0 auto;
} </style>
</head>
<body>
<canvas id="canvas" resize></canvas>
<img width="1024" height="1024" id="mona" style="display: none;" src="/web.jpg">
</body>
</html>
Is there any way to fix the initial canvas size so the result of the "scratching" isn't lost?
Your problem resides in the onResize function which is called every time the window is resized and which basically reset your drawing.
The resize attribute on the <canvas> element also is part of the problem as it makes sure that the canvas size is updated every time the window is resized.
In your case, I think that you want to get rid of those.
Here is a fiddle adapted from your code, turning it into a static drawing that doesn't respond to resize events.
<html>
<head>
<meta charset="UTF-8">
<title>Division Raster</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.2/paper-full.min.js"></script>
<script type="text/paperscript" canvas="canvas">
// Load image then init.
var raster = new Raster({
source: 'http://assets.paperjs.org/images/marilyn.jpg',
crossOrigin: 'anonymous',
onLoad: init
});
function init() {
// Make image fill the whole canvas.
raster.fitBounds(view.bounds, true);
var lastPos = view.center;
function moveHandler(event) {
if (lastPos.getDistance(event.point) < 1) {
return;
}
lastPos = event.point;
var size = this.bounds.size.clone();
var isLandscape = size.width > size.height;
// If the path is in landscape orientation, we're going to
// split the path horizontally, otherwise vertically:
size /= isLandscape ? [2, 1] : [1, 2];
if (size.ceil().width > 10) {
var path = new Path.Rectangle({
point: this.bounds.topLeft.floor(),
size: size.ceil(),
onMouseMove: moveHandler
});
path.fillColor = raster.getAverageColor(path);
var path = new Path.Rectangle({
point: isLandscape
? this.bounds.topCenter.ceil()
: this.bounds.leftCenter.ceil(),
size: size.floor(),
onMouseMove: moveHandler
});
path.fillColor = raster.getAverageColor(path);
}
this.remove();
}
// Create a path that fills the view, and fill it with
// the average color of the raster:
new Path.Rectangle({
rectangle: view.bounds,
fillColor: raster.getAverageColor(view.bounds),
onMouseMove: moveHandler
});
}
</script>
<style type="text/css">
canvas {
width : 100vw;
height : 100vh;
position : absolute;
top : 0;
left : 0;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>

Leaflet map with embedded Bootstrap Switch toggle

I am trying to add a bootstrap switch inside my leaflet.js map.
So far I have a working button (see snippet) but I want to use a switch instead.
See attached image:
So far it is a complete failure.
Among the things I have tried is the code below (which obviously does not work):
var customControl_2 = L.Control.extend({
options: {
position: 'topright'
},
onAdd: function (map) {
var container = L.DomUtil.create('input', 'mySwitch');
container = $("[class='mySwitch']").bootstrapSwitch({})
//container.onclick = function(){
// console.log('buttonClicked');
//}
return container;
}
});
map.addControl(new customControl_2());
Does anyone know how this should work please? As always, any help is greatly appreciated. If the same toggle switch can be achieved in some other way (ie without bootstrap) that is also going to be fine.
Many thanks!
<!DOCTYPE html>
<html>
<head>
<title>Leaflet</title>
<meta charset="utf-8" />
<!--jquery -->
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<!-- bootstrap -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<!-- bootstrap switch -->
<link rel="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-switch/3.3.2/css/bootstrap3/bootstrap-switch.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-switch/3.3.2/js/bootstrap-switch.js"></script>
<!--d3 -->
<script src='https://d3js.org/d3.v4.min.js'></script>
<!-- leaflet -->
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.1/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet#1.3.1/dist/leaflet.js"></script>
<style>
html,
body {
height: 100%;
margin: 0;
}
#map {
width: 600px;
height: 400px;
}
</style>
</head>
<body>
<div id='map'></div>
<script type="text/javascript">
var map = L.map('map', {
minZoom: 0,
}).setView([37, -103], 3);
var positron = L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
attribution: "CartoDB"
}).addTo(map);
// Toggle button to turn layers on and off
var customControl = L.Control.extend({
options: {
position: 'topright'
},
onAdd: function(map) {
var container = L.DomUtil.create('input');
container.type = "button";
container.title = "Some title";
container.value = "Off";
container.style.backgroundColor = 'white';
container.style.backgroundSize = "80px 30px";
container.style.width = '80px';
container.style.height = '30px';
function toggle(button) {
if (button.value == "Off") {
button.value = "On"
button.innerHTML = "On"
removeLayers();
} else if (button.value == "On") {
button.value = "Off"
button.innerHTML = "Off"
addLayers();
}
}
container.onclick = function() {
toggle(this);
console.log('buttonClicked');
}
return container;
}
});
map.addControl(new customControl());
</script>
</body>
</html>
The $("[class='mySwitch']") finds Elements based on the string selector. You have to adjust the Bootstrap Switch example to your usage. In your case, you do not need a selector but you can directly pass the HTML Element you created, so that it is wrapped by jQuery and can be transformed by Bootstrap Switch: $(container).bootstrapSwitch({})
Do not try to transform your Control container directly, but embed a child checkbox input into that container:
var container = L.DomUtil.create('div');
// Use a child input.
var input = L.DomUtil.create('input');
input.type = "checkbox";
// Insert the input as child of container.
container.appendChild(input);
// Transform the input, not the container.
$(input).bootstrapSwitch({});
You have a typo in:
<link rel="https:....css">
…should be:
<link rel="stylesheet" href="https:....css">
Live result:
var map = L.map('map', {
minZoom: 0,
}).setView([37, -103], 3);
var positron = L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
attribution: "CartoDB"
}).addTo(map);
// Toggle button to turn layers on and off
var customControl = L.Control.extend({
options: {
position: 'topright'
},
onAdd: function(map) {
var container = L.DomUtil.create('div');
// Use a child input.
var input = L.DomUtil.create('input');
input.type = "checkbox";
input.title = "Some title";
input.value = "Off";
// Insert the input as child of container.
container.appendChild(input);
jQuery(input).bootstrapSwitch({
// http://bootstrapswitch.site/options.html
onSwitchChange: function(event) {
console.log('buttonClicked', event.target.checked);
}
});
return container;
}
});
map.addControl(new customControl());
html,
body,
#map {
height: 100%;
margin: 0;
}
<!--jquery -->
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<!-- bootstrap -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<!--script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script-->
<!-- bootstrap switch -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-switch/3.3.2/css/bootstrap3/bootstrap-switch.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-switch/3.3.2/js/bootstrap-switch.js"></script>
<!-- leaflet -->
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.1/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet#1.3.1/dist/leaflet.js"></script>
<div id='map'></div>

How to position a label in the center of a leaflet tile?

I have statistical data by leaflet tile. I try to put a label in the center of a tile so that it roughly looks like this:
So far I have code like this:
<div id="map" style="height: 512px; width: 512px;border: solid;">
</div>
...
var textLatLng = [?, ?]; // This is my question, how to calculate this?
var myTextLabel = L.marker(textLatLng, {
icon: L.divIcon({
className: 'text-labels',
html: '173'
}),
zIndexOffset: 1000
});
myTextLabel.addTo(map);
Got this code from here
If I understand correctly, you're looking to extend a GridLayer and its createTile method to display your data. Something like this, assuming a synchronous lookup
var map = L.map('map').setView([48.8583736, 2.2922926], 4);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
var GridInfo = L.GridLayer.extend({
// called for each tile
// return a DOM node containing whatever you want
createTile: function (coords) {
// create a div
var tile = document.createElement('div');
tile.className = "infotile";
tile.style.outline = '1px solid black';
// lookup the piece of data you want
// replace with whatever you use
var data = lookup(coords.x, coords.y, coords.z);
// let's add the lat/lng of the center of the tile
var tileBounds = this._tileCoordsToBounds(coords);
var center = tileBounds.getCenter();
tile.innerHTML = '<span>' + data+
'<br>'+
'lat:'+ center.lat+' '+'lng:'+center.lng+
'</span>';
return tile;
}
});
map.addLayer(new GridInfo());
And a demo
function lookup(x, y, z) {
return "x:"+x+", y:"+y+" at zoom "+z;
}
var map = L.map('map').setView([48.8583736, 2.2922926], 4);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
var GridInfo = L.GridLayer.extend({
// called for each tile
// returns a DOM node containing whatver ypu want
createTile: function (coords) {
// create a div
var tile = document.createElement('div');
tile.className = "infotile";
tile.style.outline = '1px solid black';
// lookup the piece of data you want
var data = lookup(coords.x, coords.y, coords.z);
// let's add the lat/lng of the center of the tile
var tileBounds = this._tileCoordsToBounds(coords);
var center = tileBounds.getCenter();
tile.innerHTML = '<span>' + data+
'<br>'+
'lat:'+ center.lat+' '+'lng:'+center.lng+
'</span>';
return tile;
}
});
map.addLayer(new GridInfo());
html, body {
height: 100%;
margin: 0;
}
#map {
width: 100%;
height: 100%;
}
.infotile {display: flex;}
.infotile span {
font-weight: bold;
margin: auto;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.2.0/dist/leaflet.css" integrity="sha512-M2wvCLH6DSRazYeZRIm1JnYyh22purTM+FDB5CsyxtQJYeKq83arPe5wgbNmcFXGqiSH2XR8dT/fJISVA1r/zQ==" crossorigin=""/>
<script src="https://unpkg.com/leaflet#1.2.0/dist/leaflet.js" integrity="sha512-lInM/apFSqyy1o6s89K4iQUKg6ppXEgsVxT35HbzUupEVRh2Eu9Wdl4tHj7dZO0s1uvplcYGmt3498TtHq+log==" crossorigin=""></script>
<div id='map'></div>

Categories