Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmark/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="pixelmatch.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.0.1/dist/protomaps-leaflet.min.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.1.0/dist/protomaps-leaflet.min.js"></script>
<!-- <script src="../dist/protomaps-leaflet.js"></script> -->
<style>
html {
Expand Down
2 changes: 1 addition & 1 deletion examples/comparison.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl@3.3.1/dist/maplibre-gl.css" crossorigin="anonymous">
<script src="https://unpkg.com/maplibre-gl@3.3.1/dist/maplibre-gl.js" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@maplibre/maplibre-gl-leaflet@0.0.20/leaflet-maplibre-gl.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.0.1/dist/protomaps-leaflet.min.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.1.0/dist/protomaps-leaflet.min.js"></script>
<style>
#parent {
display:flex;
Expand Down
2 changes: 1 addition & 1 deletion examples/fonts.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-hash@0.2.1/leaflet-hash.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.0.1/dist/protomaps-leaflet.min.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.1.0/dist/protomaps-leaflet.min.js"></script>
<!-- <script src="../dist/protomaps-leaflet.js"></script> -->

<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=Work+Sans:wght@100..900&family=Petrona:wght@100..900&family=Raleway:wght@100..900" rel="stylesheet">
Expand Down
2 changes: 1 addition & 1 deletion examples/inset.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://unpkg.com/protomaps-leaflet@3.0.1/dist/protomaps-leaflet.min.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.1.0/dist/protomaps-leaflet.min.js"></script>
<!-- <script src="../dist/protomaps-leaflet.js"></script> -->
<style>
#map {
Expand Down
2 changes: 1 addition & 1 deletion examples/labels.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-hash@0.2.1/leaflet-hash.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.0.1/dist/protomaps-leaflet.min.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.1.0/dist/protomaps-leaflet.min.js"></script>
<!-- <script src="../dist/protomaps-leaflet.js"></script> -->
<style>
body, #map {
Expand Down
9 changes: 8 additions & 1 deletion examples/leaflet.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-hash@0.2.1/leaflet-hash.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.0.1/dist/protomaps-leaflet.min.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.1.0/dist/protomaps-leaflet.min.js"></script>
<!-- <script src="../dist/protomaps-leaflet.js"></script> -->
<style>
body, #map {
Expand All @@ -22,6 +22,13 @@
if (!window.location.hash) map.setView(new L.LatLng(0,0),0)
var layer = protomapsL.leafletLayer({url:'https://api.protomaps.com/tiles/v3/{z}/{x}/{y}.mvt?key=1003762824b9687f',theme:'light'})
layer.addTo(map)

map.on("click", (ev) => {
const wrapped = map.wrapLatLng(ev.latlng);
// note: this method supports only basic use,
// see comments in source code
console.log(layer.queryTileFeaturesDebug(wrapped.lng, wrapped.lat));
})
</script>
</body>
</html>
2 changes: 1 addition & 1 deletion examples/multi_source.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-hash@0.2.1/leaflet-hash.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.0.1/dist/protomaps-leaflet.min.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.1.0/dist/protomaps-leaflet.min.js"></script>
<!-- <script src="../dist/protomaps-leaflet.js"></script> -->
<style>
body, #map {
Expand Down
2 changes: 1 addition & 1 deletion examples/pmtiles.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.0.1/dist/protomaps-leaflet.min.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.1.0/dist/protomaps-leaflet.min.js"></script>
<!-- <script src="../dist/protomaps-leaflet.js"></script> -->
<style>
body, #map {
Expand Down
2 changes: 1 addition & 1 deletion examples/pmtiles_headers.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="https://unpkg.com/pmtiles@3.0.3/dist/pmtiles.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.0.1/dist/protomaps-leaflet.min.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.1.0/dist/protomaps-leaflet.min.js"></script>
<!-- <script src="../dist/protomaps-leaflet.js"></script> -->
<style>
body, #map {
Expand Down
2 changes: 1 addition & 1 deletion examples/sandwich.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-hash@0.2.1/leaflet-hash.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.0.1/dist/protomaps-leaflet.min.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.1.0/dist/protomaps-leaflet.min.js"></script>
<!-- <script src="../dist/protomaps-leaflet.js"></script> -->
<style>
body, #map {
Expand Down
2 changes: 1 addition & 1 deletion examples/sprites.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-hash@0.2.1/leaflet-hash.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.0.1/dist/protomaps-leaflet.min.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.1.0/dist/protomaps-leaflet.min.js"></script>
<!-- <script src="../dist/protomaps-leaflet.js"></script> -->
<style>
body, #map {
Expand Down
2 changes: 1 addition & 1 deletion examples/static.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://unpkg.com/protomaps-leaflet@3.0.1/dist/protomaps-leaflet.min.js"></script>
<script src="https://unpkg.com/protomaps-leaflet@3.1.0/dist/protomaps-leaflet.min.js"></script>
<!-- <script src="../dist/protomaps-leaflet.js"></script> -->
<style>
#map {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "protomaps-leaflet",
"version": "3.0.1",
"version": "3.1.0",
"files": [
"dist",
"src"
Expand Down
21 changes: 21 additions & 0 deletions src/frontends/leaflet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import themes from "../default_style/themes";
import { LabelRule, Labelers } from "../labeler";
import { PaintRule, paint } from "../painter";
import { PreparedTile, SourceOptions, sourcesToViews } from "../view";
import { PickedFeature } from "../tilecache";

const timer = (duration: number) => {
return new Promise<void>((resolve) => {
Expand Down Expand Up @@ -270,6 +271,26 @@ const leafletLayer = (options: LeafletLayerOptions = {}): unknown => {
}
}

// a primitive way to check the features at a certain point.
// it does not support hover states, cursor changes, or changing the style of the selected feature,
// so is only appropriate for debuggging or very basic use cases.
// those features are outside of the scope of this library:
// for fully pickable, interactive features, use MapLibre GL JS instead.
public queryTileFeaturesDebug(
lng: number,
lat: number,
brushSize = 16,
): Map<string, PickedFeature[]> {
const featuresBySourceName = new Map<string, PickedFeature[]>();
for (const [sourceName, view] of this.views) {
featuresBySourceName.set(
sourceName,
view.queryFeatures(lng, lat, this._map.getZoom(), brushSize),
);
}
return featuresBySourceName;
}

public clearLayout() {
this.labelers = new Labelers(
this.scratch,
Expand Down
143 changes: 143 additions & 0 deletions src/tilecache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,104 @@ interface PromiseOptions {
reject: (e: Error) => void;
}

export interface PickedFeature {
feature: Feature;
layerName: string;
}

const R = 6378137;
const MAX_LATITUDE = 85.0511287798;
const MAXCOORD = R * Math.PI;

const project = (latlng: number[]) => {
const d = Math.PI / 180;
const constrainedLat = Math.max(
Math.min(MAX_LATITUDE, latlng[0]),
-MAX_LATITUDE,
);
const sin = Math.sin(constrainedLat * d);
return new Point(
R * latlng[1] * d,
(R * Math.log((1 + sin) / (1 - sin))) / 2,
);
};

function sqr(x: number) {
return x * x;
}

function dist2(v: Point, w: Point) {
return sqr(v.x - w.x) + sqr(v.y - w.y);
}

function distToSegmentSquared(p: Point, v: Point, w: Point) {
const l2 = dist2(v, w);
if (l2 === 0) return dist2(p, v);
let t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
t = Math.max(0, Math.min(1, t));
return dist2(p, new Point(v.x + t * (w.x - v.x), v.y + t * (w.y - v.y)));
}

export function isInRing(point: Point, ring: Point[]): boolean {
let inside = false;
for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
const xi = ring[i].x;
const yi = ring[i].y;
const xj = ring[j].x;
const yj = ring[j].y;
const intersect =
yi > point.y !== yj > point.y &&
point.x < ((xj - xi) * (point.y - yi)) / (yj - yi) + xi;
if (intersect) inside = !inside;
}
return inside;
}

export function isCcw(ring: Point[]): boolean {
let area = 0;
for (let i = 0; i < ring.length; i++) {
const j = (i + 1) % ring.length;
area += ring[i].x * ring[j].y;
area -= ring[j].x * ring[i].y;
}
return area < 0;
}

export function pointInPolygon(point: Point, geom: Point[][]): boolean {
let isInCurrentExterior = false;
for (const ring of geom) {
if (isCcw(ring)) {
// it is an interior ring
if (isInRing(point, ring)) isInCurrentExterior = false;
} else {
// it is an exterior ring
if (isInCurrentExterior) return true;
if (isInRing(point, ring)) isInCurrentExterior = true;
}
}
return isInCurrentExterior;
}

export function pointMinDistToPoints(point: Point, geom: Point[][]): number {
let min = Infinity;
for (const l of geom) {
const dist = Math.sqrt(dist2(point, l[0]));
if (dist < min) min = dist;
}
return min;
}

export function pointMinDistToLines(point: Point, geom: Point[][]): number {
let min = Infinity;
for (const l of geom) {
for (let i = 0; i < l.length - 1; i++) {
const dist = Math.sqrt(distToSegmentSquared(point, l[i], l[i + 1]));
if (dist < min) min = dist;
}
}
return min;
}

export class TileCache {
source: TileSource;
cache: Map<string, CacheEntry>;
Expand Down Expand Up @@ -288,4 +386,49 @@ export class TileCache {
}
});
}

public queryFeatures(
lng: number,
lat: number,
zoom: number,
brushSize: number,
): PickedFeature[] {
const projected = project([lat, lng]);
const normalized = new Point(
(projected.x + MAXCOORD) / (MAXCOORD * 2),
1 - (projected.y + MAXCOORD) / (MAXCOORD * 2),
);
if (normalized.x > 1)
normalized.x = normalized.x - Math.floor(normalized.x);
const onZoom = normalized.mult(1 << zoom);
const tileX = Math.floor(onZoom.x);
const tileY = Math.floor(onZoom.y);
const idx = toIndex({ z: zoom, x: tileX, y: tileY });
const retval: PickedFeature[] = [];
const entry = this.cache.get(idx);
if (entry) {
const center = new Point(
(onZoom.x - tileX) * this.tileSize,
(onZoom.y - tileY) * this.tileSize,
);
for (const [layerName, layerArr] of entry.data.entries()) {
for (const feature of layerArr) {
if (feature.geomType === GeomType.Point) {
if (pointMinDistToPoints(center, feature.geom) < brushSize) {
retval.push({ feature, layerName: layerName });
}
} else if (feature.geomType === GeomType.Line) {
if (pointMinDistToLines(center, feature.geom) < brushSize) {
retval.push({ feature, layerName: layerName });
}
} else {
if (pointInPolygon(center, feature.geom)) {
retval.push({ feature, layerName: layerName });
}
}
}
}
}
return retval;
}
}
12 changes: 12 additions & 0 deletions src/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,18 @@ export class View {
dim: tt.dim,
};
}

public queryFeatures(
lng: number,
lat: number,
displayZoom: number,
brushSize: number,
) {
const roundedZoom = Math.round(displayZoom);
const dataZoom = Math.min(roundedZoom - this.levelDiff, this.maxDataLevel);
const brushSizeAtZoom = brushSize / (1 << (roundedZoom - dataZoom));
return this.tileCache.queryFeatures(lng, lat, dataZoom, brushSizeAtZoom);
}
}

export interface SourceOptions {
Expand Down
Loading