2025-12-01 09:43:14 +01:00

171 lines
7.4 KiB
HTML

<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="/examples/site.css" />
<script src="https://unpkg.com/underscore@1.13.1/underscore-min.js"></script>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/flatgeobuf@4.3.3/dist/flatgeobuf-geojson.min.js"></script>
<script src="https://unpkg.com/json-formatter-js@2.5.23/dist/json-formatter.umd.js"></script>
<style>
#map { height: 480px; }
</style>
</head>
<body>
<ul class="primary-navigation">
<li class="active">
Leaflet Example
</li>
<li>
<a href="/examples/openlayers/large.html">OpenLayers Example</a>
</li>
<li>
<a href="/examples/maplibre/">MapLibre Example</a>
</li>
</ul>
<ul class="secondary-navigation">
<li><a href="/examples/leaflet/">Basic Example</a></li>
<li><a href="/examples/leaflet/filtered.html">Filter By Rect</a></li>
<li class="active">Filtering a Large Dataset</li>
</ul>
<div id="map"></div>
<script>
document.addEventListener("DOMContentLoaded", async () => {
// basic OSM Leaflet map
const map = L.map('map').setView([40.766, -73.98], 14);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
minZoom: 12,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
// optionally show some meta-data about the FGB file
function handleHeaderMeta(headerMeta) {
const header = document.getElementById('header')
const formatter = new JSONFormatter(headerMeta, 10)
while (header.firstChild)
header.removeChild(header.firstChild)
header.appendChild(formatter.render())
}
// For the example, we fix a visible Rect in the middle of the map
function getBoundForRect() {
const bounds = map.getBounds();
const width = map.distance(bounds.getNorthWest(), bounds.getNorthEast());
const height = map.distance(bounds.getNorthWest(), bounds.getSouthWest());
return map.getCenter().toBounds(Math.min(width, height) * 0.8);
}
// convert the rect into the format flatgeobuf expects
function fgBoundingBox() {
const bounds = getBoundForRect();
return {
minX: bounds.getWest(),
maxX: bounds.getEast(),
minY: bounds.getSouth(),
maxY: bounds.getNorth(),
};
}
// show a leaflet rect corresponding to our bounding box
const rectangle = L.rectangle(getBoundForRect(), { interactive: false, color: "blue", fillOpacity: 0.0, opacity: 1.0 }).addTo(map);
// track the previous results so we can remove them when adding new results
let previousResults = L.layerGroup().addTo(map);
async function updateResults() {
// remove the old results
previousResults.remove();
const nextResults = L.layerGroup().addTo(map);
previousResults = nextResults;
// Use flatgeobuf JavaScript API to iterate features as geojson.
// Because we specify a bounding box, flatgeobuf will only fetch the relevant subset of data,
// rather than the entire file.
const iter = flatgeobuf.deserialize('https://flatgeobuf.septima.dk/population_areas.fgb', fgBoundingBox(), handleHeaderMeta);
const colorScale = ((d) => {
return d > 750 ? '#800026' :
d > 500 ? '#BD0026' :
d > 250 ? '#E31A1C' :
d > 100 ? '#FC4E2A' :
d > 50 ? '#FD8D3C' :
d > 25 ? '#FEB24C' :
d > 10 ? '#FED976' :
'#FFEDA0'
});
for await (const feature of iter) {
// Leaflet styling
const defaultStyle = {
color: colorScale(feature.properties.population),
weight: 1,
fillOpacity: 0.4,
};
L.geoJSON(feature, {
style: defaultStyle,
}).on({
'mouseover': (e) => {
const layer = e.target;
layer.setStyle({
weight: 4,
fillOpacity: 0.8,
});
},
'mouseout': (e) => {
const layer = e.target;
layer.setStyle(defaultStyle);
}
}).bindPopup(`${feature.properties.population} people live in this census block.</h1>`)
.addTo(nextResults);
}
rectangle.bringToFront();
}
// if the user is panning around alot, only update once per second max
const updateResultsThrottled = _.throttle(updateResults, 1000);
// show results based on the initial map
updateResultsThrottled();
// ...and update the results whenever the map moves
map.on("moveend", ()=> {
rectangle.setBounds(getBoundForRect());
updateResultsThrottled();
});
});
</script>
<p>
This is a map of every census block in the USA, including its
population. The entire file is over 12GB, but FlatGeobuf fetches only
the tiny subset of data that intersects with the bounding box (drawn
in blue). Pan the map to move the query's bounding box.
</p>
<p>
When you have feature data that cover a large area in fine-grained
detail like this, the typical options are to either manually slice up
your file into manageable regions or to rely on running an application
server which does this slicing dynamically.
</p>
<p>
Hosting an application has initial complexity and ongoing maintenance
costs. Slicing files can be tedious and inevitiably you might be
interested in an area on the boundary of slices.
</p>
<p>
For these cases, consider instead using a single indexed FlatGeobuf.
Because FlatGeobuf's spatial index allows you to fetch only the data
you're interested in, you can keep your page size down while avoiding
the tedium of slicing up files manually, or building and maintaining an
application server.
</p>
<p>
Though getting a small subset of features from a large dataset transfers
relatively little data, it does so using multiple requests. There are some
<a href="https://github.com/flatgeobuf/flatgeobuf?tab=readme-ov-file#optimizing-remotely-hosted-flatgeobufs">tips for optimizing remote hosted flatgeobufs</a>, mostly oriented around minimizing round trip latency.
</p>
<div id="header">
<h3>Parsed header content</h3>
</div>
</body>
</html>