* Load svg icon appears while data is loading
* Uncompressed geojson file is 29 MB on dropbox
* Total time to load depends on client internet speed & client GPU
* Geojson source is split into two parts to optimize load and render time
Last active
February 3, 2023 08:05
-
-
Save ryanbaumann/04c442906638e27db9da243f29195592 to your computer and use it in GitHub Desktop.
Example of loading large geojson into Mapbox GL JS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset='utf-8' /> | |
<title></title> | |
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' /> | |
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v2.6.0/mapbox-gl.js'></script> | |
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v2.6.0/mapbox-gl.css' rel='stylesheet' /> | |
<link href='https://www.mapbox.com/base/latest/base.css' rel='stylesheet' /> | |
<style> | |
body { | |
margin: 0; | |
padding: 0; | |
} | |
#map { | |
position: absolute; | |
top: 0; | |
bottom: 0; | |
width: 100%; | |
} | |
.map-overlay { | |
position: absolute; | |
width: 180px; | |
top: 0; | |
left: 10px; | |
padding: 10px; | |
margin-left: 5px; | |
margin-top: 2px; | |
margin-bottom: 2px; | |
margin-right: 5px; | |
z-index: 1; | |
} | |
.map-overlay .map-overlay-inner { | |
background: rgba(0, 0, 0, .8); | |
color: #fff; | |
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.10); | |
border-radius: 3px; | |
padding: 10px; | |
margin-bottom: 10px; | |
z-index: 1; | |
} | |
.map-overlay-inner fieldset { | |
border: none; | |
padding: 0; | |
margin: 0 0 10px; | |
z-index: 1; | |
} | |
/* Dark attribution */ | |
.mapboxgl-ctrl.mapboxgl-ctrl-attrib { | |
background: rgba(0, 0, 0, .8); | |
} | |
.mapboxgl-ctrl.mapboxgl-ctrl-attrib a { | |
color: #fff; | |
} | |
/* Dark popup */ | |
.mapboxgl-popup-content { | |
background-color: #202020; | |
color: #fff; | |
margin-left: 5px; | |
margin-top: 2px; | |
margin-bottom: 2px; | |
margin-right: 5px; | |
z-index: 1000; | |
} | |
.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip, | |
.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip, | |
.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip { | |
border-top-color: #202020; | |
} | |
.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip, | |
.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip, | |
.mapboxgl-popup-anchor-top .mapboxgl-popup-tip { | |
border-bottom-color: #202020; | |
} | |
.mapboxgl-popup-anchor-right .mapboxgl-popup-tip { | |
border-left-color: #202020; | |
} | |
.mapboxgl-popup-anchor-left .mapboxgl-popup-tip { | |
border-right-color: #202020; | |
} | |
#popup-menu ul, | |
#menu li { | |
margin: 0; | |
padding: 0; | |
z-index: 100; | |
} | |
.mapboxgl-ctrl-group { | |
-webkit-filter: invert(100%); | |
} | |
.loader { | |
margin: -10px 0 0 -250px; | |
height: 100px; | |
width: 20%; | |
position: fixed; | |
text-align: center; | |
padding: 1em; | |
top: 50%; | |
left: 50%; | |
margin: 0 auto 1em; | |
z-index: 9999; | |
} | |
/* | |
Set the color of the icon | |
*/ | |
svg path, | |
svg rect { | |
fill: #FF6700; | |
} | |
</style> | |
</head> | |
<body> | |
<div id='map'></div> | |
<div class='map-overlay top'> | |
<div class='map-overlay-inner'> | |
<fieldset> | |
<label><b>Select property</b></label> | |
<select id='prop' name='prop'> | |
<option value='CYC_INJ'>Cyclist Injuries</option> | |
<option value='CYC_KIL'>Cyclist Fatalities</option> | |
<option value='PED_INJ'>Pedestrian Injuries</option> | |
<option value='PED_KIL'>Pedestrian Fatalities</option> | |
</select> | |
</fieldset> | |
<b><a href="https://data.cityofnewyork.us/Public-Safety/NYPD-Motor-Vehicle-Collisions/h9gi-nx95" | |
target="_blank">NYC Open Data</a></b> | |
</div> | |
</div> | |
<div class="loader loader--style1" title="0" id="loader"> | |
<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" | |
x="0px" y="0px" width="40px" height="40px" viewBox="0 0 40 40" enable-background="new 0 0 40 40" | |
xml:space="preserve"> | |
<path opacity="0.2" fill="#000" | |
d="M20.201,5.169c-8.254,0-14.946,6.692-14.946,14.946c0,8.255,6.692,14.946,14.946,14.946 | |
s14.946-6.691,14.946-14.946C35.146,11.861,28.455,5.169,20.201,5.169z M20.201,31.749c-6.425,0-11.634-5.208-11.634-11.634 | |
c0-6.425,5.209-11.634,11.634-11.634c6.425,0,11.633,5.209,11.633,11.634C31.834,26.541,26.626,31.749,20.201,31.749z" /> | |
<path fill="#000" d="M26.013,10.047l1.654-2.866c-2.198-1.272-4.743-2.012-7.466-2.012h0v3.312h0 | |
C22.32,8.481,24.301,9.057,26.013,10.047z"> | |
<animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 20 20" | |
to="360 20 20" dur="0.5s" repeatCount="indefinite" /> | |
</path> | |
</svg> | |
</div> | |
<script> | |
mapboxgl.accessToken = 'pk.eyJ1IjoicnNiYXVtYW5uIiwiYSI6ImNqNmhkZnhkZDA4M3Yyd3AwZDR4cmdhcDIifQ.TGKKAC6pPP0L-uMDJ5xFAA'; | |
var bounds = [ | |
[-75.04728500751165, 39.68392799015035], | |
[-72.91058699000139, 41.87764500765852] | |
]; | |
var map = new mapboxgl.Map({ | |
container: 'map', | |
style: 'mapbox://styles/mapbox/dark-v10', | |
center: [-74.0059, 40.7128], | |
zoom: 10, | |
minZoom: 9, | |
maxZoom: 18, | |
pitch: 40, | |
maxBounds: bounds | |
}); | |
function init() { | |
map.addSource('veh-incidents-1', { | |
type: 'geojson', | |
data: 'https://data.cityofnewyork.us/resource/h9gi-nx95.geojson', | |
buffer: 0, | |
maxzoom: 16 | |
}); | |
if (window.location.search.indexOf('embed') !== -1) map.scrollZoom.disable(); | |
map.addLayer({ | |
'id': 'veh-incd-1', | |
'type': 'circle', | |
'source': 'veh-incidents-1', | |
'paint': { | |
'circle-color': { | |
property: 'CYC_INJ', | |
type: 'interval', | |
stops: [ | |
[1, 'orange'], | |
[2, 'red'] | |
] | |
}, | |
'circle-radius': { | |
property: 'CYC_INJ', | |
base: 3, | |
type: 'interval', | |
stops: [ | |
[1, 3], | |
[2, 8], | |
[3, 12] | |
] | |
}, | |
'circle-opacity': 0.8, | |
'circle-blur': 0.5 | |
}, | |
'filter': ['>=', 'CYC_INJ', 1] | |
}, 'waterway-label'); | |
map.addLayer({ | |
'id': 'veh-incd-base-1', | |
'type': 'circle', | |
'source': 'veh-incidents-1', | |
'paint': { | |
'circle-color': 'yellow', | |
'circle-radius': 3, | |
'circle-opacity': 0.3, | |
'circle-blur': 1 | |
}, | |
'filter': ['<', 'CYC_INJ', 1] | |
}, 'waterway-label'); | |
map.once('style.load', function (e) { | |
init(); | |
map.addControl(new mapboxgl.NavigationControl()); | |
map.on('click', function (e) { | |
var features = map.queryRenderedFeatures(e.point, { | |
layers: ['veh-incd-1', 'veh-incd-2'] | |
}); | |
if (!features.length) { | |
return; | |
} | |
var feature = features[0]; | |
var popup = new mapboxgl.Popup() | |
.setLngLat(map.unproject(e.point)) | |
.setHTML('<h3>Collision Detail</h3>' + | |
'<ul>' + | |
'<li>Year: <b>' + feature.properties.YEAR + '</b></li>' + | |
'<li>Pedestrian Injuries: <b>' + feature.properties.PED_INJ + '</b></li>' + | |
'<li>Pedestrian Fatalities: <b>' + feature.properties.PED_KIL + '</b></li>' + | |
'<li>Cyclist Injuries: <b>' + feature.properties.CYC_INJ + '</b></li>' + | |
'<li>Cyclist Fatalities: <b>' + feature.properties.CYC_KIL + '</b></li>' + | |
'</ul>') | |
.addTo(map); | |
}); | |
//Hide loading bar once tiles from geojson are loaded | |
map.on('data', function (e) { | |
if (e.dataType === 'source' && e.sourceId === 'veh-incidents-1') { | |
document.getElementById("loader").style.visibility = "hidden"; | |
} | |
}) | |
// Use the same approach as above to indicate that the symbols are clickable | |
// by changing the cursor style to 'pointer'. | |
map.on('mousemove', function (e) { | |
var features = map.queryRenderedFeatures(e.point, { | |
layers: ['veh-incd-1', 'veh-incd-2'] | |
}); | |
map.getCanvas().style.cursor = (features.length) ? 'pointer' : ''; | |
}); | |
var prop = document.getElementById('prop'); | |
prop.addEventListener('change', function () { | |
map.setPaintProperty('veh-incd-1', 'circle-color', { | |
property: prop.value, | |
type: 'interval', | |
stops: [ | |
[1, 'orange'], | |
[2, 'red'] | |
] | |
}); | |
map.setPaintProperty('veh-incd-1', 'circle-radius', { | |
property: prop.value, | |
base: 3, | |
type: 'interval', | |
stops: [ | |
[1, 3], | |
[2, 6], | |
[3, 9] | |
] | |
}); | |
map.setFilter('veh-incd-1', ['>=', prop.value, 1]); | |
}); | |
}); | |
} | |
</script> | |
</body> | |
</html> |
Not at all! Happy to update this example as well since people find it useful. Thank you both.
Hi @ryanbaumann,
Question regarding Geojson source is split into two parts to optimize load and render time
Thank you for the example. We have some similar requirement to load large geojson to mapboxgl. Having said that, We are considering splitting our geo json into smaller chunks.
That is the reason i stumbled across your example. However, i am trying to understand where exactly we have splitted geo json in your example and how are we fetching the other pieces of it and appending to map source?
I see we addSource just once. 'https://data.cityofnewyork.us/resource/h9gi-nx95.geojson'
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for the example @ryanbaumann!
@owenabrams, I had the same problem since this relied on a custom minified version of the nyc.gov data, but I couldn't ever download the linked dropbox file either. I uploaded two minified versions to a new repo:
https://github.com/iisstizzy/Example-Large-geojson-in-Mapbox-GL-JS
(@ryanbaumann - Sorry, I forked this gist, but my ocd wants the relevant geojson data in a repo with the demo itself. Let me know if you'd rather me take it down, . Thanks again for the example! :)