I'm currently working on my first real outing using Javascript to build an interactive map of our customer data .
So Far I've got the basics working but the performance starts to drop when I start going above around 500 poi's with markers or 10,000 with circle markers.... if anyone could offer some advise on how to optimize what I've already got or maybe am i best to move to a proper DB like mongo for the json data or do the work server side with Node Js maybe?
Any advice would be much appreciated :)
var apiKey = 'BC9A493B41014CAABB98F0471D759707',
styleID = '108219';
// styleID = '997';
// var map = L.map('map').setView([54.550, -4.433], 7);
var southWest = new L.LatLng(61.029031, 4.746094),
northEast = new L.LatLng(48.786962 ,-13.183594),
bounds = new L.LatLngBounds(southWest, northEast);
var mapcenter = new L.LatLng(53.457393,-2.900391);
var map = new L.Map('map',
{
center: mapcenter,
zoom: 7,
// maxBounds: bounds,
zoomControl: false
});
var cloudmadeUrl = generateTileURL(apiKey, styleID),
attribution = 'Map data © OpenStreetMap contributors.',
tileLayer = new L.TileLayer(
cloudmadeUrl,
{
maxZoom: 18,
attribution: attribution,
});
tileLayer.addTo(map);
var zoomControl = new L.Control.Zoom({ position: 'topleft'} );
zoomControl.addTo(map);
var scaleControl = new L.Control.Scale({ position: 'bottomleft' });
scaleControl.addTo(map);
geojsonLayer = L.geoJson(geojson, {
pointToLayer: function(feature, latlng) {
return new L.CircleMarker(latlng, {fillColor: feature.properties.MarkerColour, fillOpacity: 0.5, stroke: false, radius: 6});
// return new L.Marker(latlng, {icon: L.AwesomeMarkers.icon({icon: feature.properties.MarkerIcon, color: feature.properties.MarkerColour, iconColor: 'white'}) });
},
onEachFeature: function (feature, layer) {
layer.bindPopup( '<strong><b>Customer Data</b></strong><br />' + '<b>Result : </b>' + feature.properties.Result + '<br />' + '<b>Postcode : </b>' + feature.properties.Postcode + '<br />' );
}
});
console.log('starting: ' + window.performance.now());
map.addLayer(geojsonLayer);
console.log('ending: ' + window.performance.now());
function generateTileURL(apiKey, styleID) {
return 'http://{s}.tile.cloudmade.com/' + apiKey + '/' + styleID + '/256/{z}/{x}/{y}.png';
}
and some sample data :
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-0.213467,
51.494815
]
},
"properties": {
"DateTime": "1372719435.39",
"Result": "Cable Serviceable",
"MarkerIcon": "ok-sign",
"MarkerColour": "green",
"Postcode": "W14 8UD"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-0.389445,
51.512121
]
},
"properties": {
"DateTime": "1372719402.083",
"Result": "Refer for National Serviceability",
"MarkerIcon": "minus-sign",
"MarkerColour": "red",
"Postcode": "UB1 1NJ",
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-0.411291,
51.508012
]
},
"properties": {
"DateTime": "1372719375.725",
"Result": "Cable Serviceable",
"MarkerIcon": "ok-sign",
"MarkerColour": "green",
"Postcode": "UB3 3JJ"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-2.11054,
53.500752
]
},
"properties": {
"DateTime": "1372719299.088",
"Result": "Cable Serviceable",
"MarkerIcon": "ok-sign",
"MarkerColour": "green",
"Postcode": "OL7 9LR",
}
}
There are a couple of Leaflet plugins that help deal with rendering large amounts of points in the client's browser.
The simplest way is to use a plugin that clusters the markers such as Marker Clusterer. Clusterer helps the rendering on the client side greatly as it means the client computer doesn't have to draw 10,000 points, it just draws 10-40.
You could also do a Heatmap - there are two plugins for that, both based on HTML5 Canvas:
HeatCanvas
Heatmap.js
Related
I am totally stuck with my WGS 84 / EPSG:3857 coordinates and display them on Leaflet.
I have Geojson with coordinates.
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
"6690861",
"682187"
]
},
"properties": {
"id": "908",
"message": "105",
"date": "",
"place": "",
"shape": ""
}
}
Now i want it display on Leaflet. But nothing show up. I search already 5 hours and find something about Proj4. Also no errors showing up.
My script code:
var map = L.map('map').setView([52.2129919, 5.2793703], 8);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {attribution: 'Map data © OpenStreetMap'}).addTo(map);
// GeoJSON layer (UTM15)
proj4.defs('EPSG:3857');
async function addGeoJson() {
const response = await fetch("geojs.php");
const data = await response.json();
L.geoJson(data).addTo(map);
var layerGroup = L.geoJSON(data, {
onEachFeature: function (feature, layer) {
layer.bindPopup('<h1>'+feature.properties.message+'</h1><p>Datum: '+feature.properties.date+'</p>');
}
}).addTo(map);
}
addGeoJson();
It's for my the first time i work with this coordinates. With lat/long coordinates was don't have problems. And just started with javascript.
Kind regards,
I might be a bit late, but following the documentation of Proj4, I would say that you need to add the crs to your geojson, like so :
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
"6690861",
"682187"
]
},
"properties": {
"id": "908",
"message": "105",
"date": "",
"place": "",
"shape": ""
},
"crs": {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:EPSG::3857"
}
}
}
Also, I think it's
L.Proj.geoJson(data).addTo(map);
instead of
L.geoJson(data).addTo(map);
I tried L.geoJson on my code and it didn't show anything contrary to L.Proj.geoJson so it might be your problem here.
I've been following this tutorial https://docs.mapbox.com/help/tutorials/custom-markers-gl-js/
My markers is showing fine and I can click on the markers to show a popup, but I would like the popups to always be shown.
I've successfully modified the CSS to not show the arrow and "x / close" button.
var geojson = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-77.032, 38.913]
},
"properties": {
"title": "Mapbox",
"description": "Washington, D.C."
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-122.414, 37.776]
},
"properties": {
"title": "Mapbox",
"description": "San Francisco, California"
}
}]
};
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
center: [-96, 37.8],
zoom: 3
});
// add markers to map
geojson.features.forEach(function(marker) {
// create a HTML element for each feature
var el = document.createElement('div');
el.className = 'marker';
// make a marker for each feature and add it to the map
new mapboxgl.Marker(el)
.setLngLat(marker.geometry.coordinates)
.setPopup(new mapboxgl.Popup({offset: 25}) // add popups
.setHTML('<h3>' + marker.properties.title + '</h3><p>' + marker.properties.description + '</p>'))
.addTo(map);
});
Any ideas?
I've successfully modified the CSS to not show the arrow and "x / close" button
To hide the "x" button, you could also use the closeButton option (see API reference).
but I would like the popups to always be shown.
Use togglePopup() which "opens or closes the bound popup, depending on the current state":
new mapboxgl.Marker(el)
.setLngLat(marker.geometry.coordinates)
.setPopup(new mapboxgl.Popup({closeOnClick: false, closeButton: false}).setText("some text"))
.addTo(map)
.togglePopup();
Created some markers using an array of GeoJSON data:
$.getJSON("GetLocationsServlet", function(data) {
L.geoJSON(data, {
onEachFeature: onEachFeature
}).addTo(mymap);
});
The GeoJSON data is like this:
[
{ "type": "Feature", "properties": { "name": "Riverway Sport Complex", "amenity": "GYM", "popupContent": "Riverway Sport Complex" }, "geometry": { "type": "Point", "coordinates": [-123.002846, 49.205036] },"id" : "1"} ,
{ "type": "Feature", "properties": { "name": "Imperial#Patterson", "amenity": "GYM", "popupContent": "Imperial#Patterson" }, "geometry": { "type": "Point", "coordinates": [-123.01249, 49.22193] },"id" : "2"}
]
The markers were successfully created and showed on the map. At some point later, I needed to iterate through all Markers, so I used eachLayer function:
var mymap = L.map('mapid').locate({setView: true, maxZoom: 15});
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png? '
access_token=..., {
maxZoom: 25,
attribution:
id: 'mapbox.streets',
}).addTo(mymap);
.......
$.getJSON( L.geoJSON()... )
.......
mymap.eachLayer(function(layer) {
alert (layer.options.id);
// Above alert successfully print out the tileLayer ID
if (layer instanceof L.Marker) {
alert("Marker [" + layer.options.title + "]");
}
});
However, it only looped through the main map tileLayer and stopped.
Am I using the eachLayer method correctly? Marker is also a subclass of Layer?
Thanks,
I have data of, say, density over 30 districts from 2000 to 2010.
I'd like to make an interactive choropleth map for each year and then either use a slider (ideally) or a radio button to select between years.
I can get interactivity on the first year, but NOT on the layers for other years.
You can see a working example here, but let me put some details below:
For simplicity, consider just two years. blocks1995 has the non-overlapping polygons BlockA and BlockB (the two districts) and blocks1996 has the same blocks. They have a property called density that produces the choropleth:
var blocks1995 = {
"type": "FeatureCollection",
"crs": {
"type": "name",
"properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" }
},
"features": [{
"type": "Feature",
"properties": { "time": 1995, "density": 3.1, "nameB": "BlockA" },
"geometry": {
"type": "Polygon",
"coordinates": [[[50.0, 29.0],[50.0, 29.99],[50.51, 29.99],[50.0, 29.0]]]
}
}, {
"type": "Feature",
"properties": { "time": 1995, "density": 1.1, "nameB": "BlockB" },
"geometry": {
"type": "Polygon",
"coordinates": [[[50.01, 30.0],[50.52, 30.0],[50.52, 30.5]]]
}
}]
};
var blocks1996 = {
"type": "FeatureCollection",
"crs": {
"type": "name",
"properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" }
},
"features": [{
"type": "Feature",
"properties": {"year": 1996, "density": 2.2, "name": "BlockA" },
"geometry": {
"type": "Polygon",
"coordinates": [[[50.0, 29.0],[50.0, 29.99],[50.51, 29.99]]]
}
}, {
"type": "Feature",
"properties": {"year": 1996,"density": 1.2,"name": "BlockB"},
"geometry": {
"type": "Polygon",
"coordinates": [[[50.01, 30.0],[50.52, 30.0],[50.52, 30.5]]]
}
}]
};
I've tried adding them to an OverlayLayer
var blocks1995Layer = L.layerGroup([ L.geoJson(blocks1995)]),
blocks1996Layer = L.layerGroup([ L.geoJson(blocks1996)]);
var overlayMaps = {
"Blocks 1995": blocks1995Layer,
"Blocks 1996": blocks1996Layer
};
var map = new L.map('map', {layers:[blocks1995Layer]})
.setView([29, 50], 7);
I put the boilerplate interactivity code found in this Leaflet interactive choropleth tutorial and then I add back to the map:
geojson = L.geoJson(blocks1995, {
style: density_style,
onEachFeature: addToFeature
}).addTo(map);
L.control.layers(null, overlayMaps).addTo(map);
The problem is I'm adding interactivity to blocks1995 only, but I haven't been able to add it to overlayMaps.
I'm OK using a Leaflet plug-in (I tried TimeSlider but also couldn't figure it out).
Another possibility is to combine the two block1995and block1996 variables into one with an extra feature year or time is that makes things easier. The idea would be for Leaflet to query by time (say, when a slider moves) and produce the interactive choropleth per year.
Thanks!
Basically, you're not adding layers to control properly. Currently, you're doing this
var blocks1995Layer = L.layerGroup([ L.geoJson(blocks1995)]),
blocks1996Layer = L.layerGroup([ L.geoJson(blocks1996)]);
var overlayMaps = {
"Blocks 1995": blocks1995Layer,
"Blocks 1996": blocks1996Layer
};
geojson = L.geoJson(blocks1995, {
style: density_style,
onEachFeature: addToFeature
}).addTo(map);
Instead, try this
geojson = L.geoJson(blocks1995, {
style: density_style,
onEachFeature: addToFeature
}).addTo(map);
geojson1 = L.geoJson(blocks1996, {
style: density_style,
onEachFeature: addToFeature
}).addTo(map);
var overlayMaps = {
"Blocks 1995": geojson,
"Blocks 1996": geojson1
};
Here is a working example
Here is another example where I've implemented radio buttons instead of checkboxes using this plugin
Edited
As per your comment, I've created a example using this leaflet time slider plugin. Here is the part of the code.
//I've created 5 geojson layers, in order the slider to look appropriate.
geojson = L.geoJson(blocks1995, {
style: density_style,
onEachFeature: addToFeature,
time: "1995" //this is for labeling, you may alter this value if required
});
geojson1 = L.geoJson(blocks1996, {
style: density_style,
onEachFeature: addToFeature,
time: "1996"
});
geojson2 = L.geoJson(blocks1997, {
style: density_style,
onEachFeature: addToFeature,
time: "1997"
});
geojson3 = L.geoJson(blocks1998, {
style: density_style,
onEachFeature: addToFeature,
time: "1998"
});
geojson4 = L.geoJson(blocks1999, {
style: density_style,
onEachFeature: addToFeature,
time: "1999"
});
//now add each geojson layer to a single layer group, as the slider take only one layer
var layerGroup = L.layerGroup([geojson, geojson1, geojson2, geojson3, geojson4 ]);
//initiate slider, follow = 1 means, show one feature at a time
var sliderControl = L.control.sliderControl({layer:layerGroup, follow: 1});
map.addControl(sliderControl);//add slider to map
sliderControl.startSlider();//starting slider
Here is the working example
i am implementing real time markers using leaflet.js version 0.7.7 and leaflet-realtime - v1.3.0. its working fine. But on map load i need to open popup for all markers.
i used .openPopup() and openOn() but not working for me.
My fiddle is:
https://jsfiddle.net/chk1/hmyxb6ur/
var map = L.map('map').setView([48.517,18.255], 5);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
var shipLayer = L.layerGroup();
var ships = L.icon({
iconUrl: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/Emoji_u1f6a2.svg/30px-Emoji_u1f6a2.svg.png',
iconSize: [30, 30]
});
var realtime = L.realtime(
/*
I'm providing a function to simulate a GeoJSON service,
instead of an URL
{
url: 'jsonServlet/ships.json',
crossOrigin: true,
type: 'json'
}*/
function(success, error){
var ship = mockShip();
success(ship);
}, {
interval: 5 * 1000,
getFeatureId: function(featureData){
return featureData.properties.mmsi;
},
pointToLayer: function (feature, latlng) {
marker = L.marker(latlng, {icon: ships});
marker.bindPopup('mmsi: ' + feature.properties.mmsi +
'<br/> course:' + feature.properties.hdg+
'<br/> speed:' + feature.properties.sog);
marker.addTo(shipLayer);
return marker;
}
}).addTo(map);
//controlLayers.addOverlay(geojson, 'Ships');
realtime.on('update', function() {
map.fitBounds(realtime.getBounds(), {maxZoom: 5});
});
function mockShip() {
return {
"type": "FeatureCollection",
"crs": {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
}
},
"features": [
{
"geometry": {
"coordinates": [
48.517+Math.sin((new Date).getTime())*2,
18.255
],
"type": "Point"
},
"type": "Feature",
"properties": {
"geometry/coordinates/longitude": "48.517708",
"geometry/type": "Point",
"mmsi": "512131345",
"geometry/coordinates/latitude": "18.255447",
"hdg": "108",
"cog": "108",
"sog": "30.0",
"type": "Feature"
}
},
{
"geometry": {
"coordinates": [
48.415,
18.151+Math.sin((new Date).getTime())*2
],
"type": "Point"
},
"type": "Feature",
"properties": {
"geometry/coordinates/longitude": "48.417708",
"geometry/type": "Point",
"mmsi": "612131346",
"geometry/coordinates/latitude": "18.155447",
"hdg": "108",
"cog": "108",
"sog": "30.0",
"type": "Feature"
}
}
]
};
}
I'll quote the Leaflet API documentation:
Use Map#openPopup to open popups while making sure that only one popup is open at one time (recommended for usability), or use Map#addLayer to open as many as you want.