Home button fits all of Slovakia; add an overview map, top-right
Home now fitBounds()s a country-wide bbox instead of resetting to CENTER/ZOOM's Bratislava point, since Home resetting into the same city you'd already be looking at wasn't useful. The overview map is NPS's own overview plugin (npmap5-plugins, still on an unmerged branch), vendored and converted from its UMD dist build to a plain ES module. Patched one MapLibre API break found while verifying it: the plugin reads map.transform (public in the MapLibre ~2.4 it targets) to relax the inset's polar pan limit; MapLibre 6 (this app's vendored version) no longer exposes .transform at all, so it threw on load. Guarded to skip that cosmetic adjustment when absent instead.
This commit is contained in:
1 parent
ddf2ee4990
commit
96065b1237
7 files changed
+441
-31
No files matched your search
+29
-15
@@ -137,26 +137,40 @@ Now that any view can be the starting one (via that permalink), a way
|
|||||||
back to the app's actual default is worth having: a "Home" button
|
back to the app's actual default is worth having: a "Home" button
|
||||||
(`public/vendor/npmap-home-button/`, the National Park Service's own
|
(`public/vendor/npmap-home-button/`, the National Park Service's own
|
||||||
`home-button` MapLibre control — see `docs/third-party-notices.adoc`)
|
`home-button` MapLibre control — see `docs/third-party-notices.adoc`)
|
||||||
sits top-left on the map, pinned to `CENTER`/`ZOOM` explicitly rather
|
sits top-left on the map. It fits to `SLOVAKIA_BOUNDS` (a country-wide
|
||||||
than its own fallback (the view at construction time, which would just
|
bounding box) rather than the plain `CENTER`/`ZOOM` point the map
|
||||||
be wherever a loaded permalink happened to point). It resets to the
|
itself starts at — "home" is meant as a full-country overview, not a
|
||||||
app's default view, not the visitor's own location — that's
|
re-centering on Bratislava. It resets to that fixed default view, not
|
||||||
`GeolocateControl`, a separate button in the same stack (below).
|
the visitor's own location — that's `GeolocateControl`, a separate
|
||||||
|
button in the same stack (below).
|
||||||
|
|
||||||
That Home button is the top of a small top-left stack — Home, zoom
|
That Home button is the top of a small top-left stack — Home, zoom
|
||||||
in/out (`NavigationControl`, `showCompass: false` since this map never
|
in/out (`NavigationControl`, `showCompass: false` since this map never
|
||||||
rotates), a passive zoom-level readout (`ZoomLevelControl`, a plain div
|
rotates), a passive zoom-level readout (`ZoomLevelControl`, a plain div
|
||||||
updated on the map's `zoom` event — not a button, since it has no
|
updated on the map's `zoom` event — not a button, since it has no
|
||||||
click behavior of its own), locate-me (`GeolocateControl`), and a
|
click behavior of its own), a distance/area measuring tool
|
||||||
"Base map" button — matching the National Park Service's own map
|
(`MeasureControl`, `public/js/measure.js`), locate-me
|
||||||
control layout (grouped
|
(`GeolocateControl`), and a "Base map" button — matching the National
|
||||||
together on the map itself, rather than split across corners or
|
Park Service's own map control layout (grouped together on the map
|
||||||
between the map and a side panel). The last of those,
|
itself, rather than split across corners or between the map and a side
|
||||||
`MapLayersButton` (`public/js/map.js`), is a second entry point into
|
panel). The last of those, `MapLayersButton` (`public/js/map.js`), is
|
||||||
the exact same Online/Offline panel `LayerToggleControl` already
|
a second entry point into the exact same Online/Offline panel
|
||||||
manages in the page's own sidebar — both call the same
|
`LayerToggleControl` already manages in the page's own sidebar — both
|
||||||
`LayerToggleControl#toggle()`, so there's one source of truth for the
|
call the same `LayerToggleControl#toggle()`, so there's one source of
|
||||||
panel's open/closed state either way.
|
truth for the panel's open/closed state either way.
|
||||||
|
|
||||||
|
A separate control sits top-right: a small inset overview map
|
||||||
|
(`public/vendor/npmap-overview/`, NPS's `overview` plugin — see
|
||||||
|
`docs/nps-resources.adoc`) showing the current view's location in a
|
||||||
|
wider, zoomed-out context, with the visible area drawn as a box on top
|
||||||
|
of it; dragging that box pans the main map. It clones whichever base
|
||||||
|
style the main map is currently showing (`style: ''`, its own default —
|
||||||
|
the plugin reads `map.style.serialize()`) rather than a separate style
|
||||||
|
of its own, so it stays visually consistent with the Online/Offline
|
||||||
|
toggle without this app maintaining a second style for it. Vendored
|
||||||
|
from an unmerged upstream branch and patched for a MapLibre API break
|
||||||
|
(it targeted MapLibre ~2.4's public `map.transform`, since removed) —
|
||||||
|
see `docs/third-party-notices.adoc` for the exact patch.
|
||||||
|
|
||||||
=== Debug inspector (Alt+click)
|
=== Debug inspector (Alt+click)
|
||||||
|
|
||||||
|
|||||||
+13
-8
@@ -27,6 +27,10 @@ checking here before searching from zero next time.
|
|||||||
| Park Tiles color palette (offline basemap theme)
|
| Park Tiles color palette (offline basemap theme)
|
||||||
| `public/js/map.js`'s `OFFLINE_THEME`
|
| `public/js/map.js`'s `OFFLINE_THEME`
|
||||||
| Colors only — see <<park-tiles>> for why the actual tile service isn't used
|
| Colors only — see <<park-tiles>> for why the actual tile service isn't used
|
||||||
|
|
||||||
|
| `overview` (top-right minimap/inset control)
|
||||||
|
| `public/vendor/npmap-overview/`
|
||||||
|
| MIT (per that branch's `package.json`); same public-domain repo as above
|
||||||
|===
|
|===
|
||||||
|
|
||||||
See `docs/third-party-notices.adoc` for the formal vendored-library
|
See `docs/third-party-notices.adoc` for the formal vendored-library
|
||||||
@@ -99,18 +103,19 @@ https://github.com/nationalparkservice/npmap5-plugins/issues/7[#7].
|
|||||||
|
|
||||||
=== On unmerged feature branches (less vetted — not on `main` yet)
|
=== On unmerged feature branches (less vetted — not on `main` yet)
|
||||||
|
|
||||||
|
`overview` (branch `maplibre-gl-overview`) has since been adopted —
|
||||||
|
see "What this app actually uses" above and
|
||||||
|
`docs/architecture.adoc`'s control-stack section. Live demo (as
|
||||||
|
upstream ships it, unpatched):
|
||||||
|
https://raw.githack.com/nationalparkservice/npmap5-plugins/maplibre-gl-overview/examples/index.html.
|
||||||
|
Note: MapLibre GL JS has no first-party minimap of its own — this
|
||||||
|
isn't more "official" than any other third-party minimap plugin, just
|
||||||
|
NPS-flavored.
|
||||||
|
|
||||||
[cols="1,3"]
|
[cols="1,3"]
|
||||||
|===
|
|===
|
||||||
| Plugin | What it is
|
| Plugin | What it is
|
||||||
|
|
||||||
| `overview` (branch `maplibre-gl-overview`)
|
|
||||||
| A minimap/inset control, any corner, respects pitch/bearing. Live
|
|
||||||
demo: https://raw.githack.com/nationalparkservice/npmap5-plugins/maplibre-gl-overview/examples/index.html.
|
|
||||||
Note: MapLibre GL JS has no first-party minimap of its own — this
|
|
||||||
wouldn't be more "official" than any other third-party minimap
|
|
||||||
plugin, just NPS-flavored. No minimap has been requested for this
|
|
||||||
app; would be a nice-to-have, not a fix for anything.
|
|
||||||
|
|
||||||
| `carto` (branch `maplibre-gl-carto-source`)
|
| `carto` (branch `maplibre-gl-carto-source`)
|
||||||
| Adds a vector-tile source backed by Carto.com-hosted data. Only
|
| Adds a vector-tile source backed by Carto.com-hosted data. Only
|
||||||
useful if we actually have a Carto-hosted dataset to point at — we
|
useful if we actually have a Carto-hosted dataset to point at — we
|
||||||
|
|||||||
@@ -36,8 +36,21 @@ required):
|
|||||||
| NPS Home Button (`home-button`)
|
| NPS Home Button (`home-button`)
|
||||||
| 0.0.1-d
|
| 0.0.1-d
|
||||||
| https://github.com/nationalparkservice/npmap5-plugins/tree/main/home-button
|
| https://github.com/nationalparkservice/npmap5-plugins/tree/main/home-button
|
||||||
|
|
||||||
|
| NPS Overview Map (`maplibre-gl-overview`)
|
||||||
|
| 5.0.1
|
||||||
|
| https://github.com/nationalparkservice/npmap5-plugins/tree/maplibre-gl-overview
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
The overview map is vendored from an unmerged branch, not `main` (see
|
||||||
|
`docs/nps-resources.adoc`), and converted from its UMD dist build to a
|
||||||
|
plain ES module — no functional change except the module wrapper. It
|
||||||
|
was also patched for a MapLibre API break: it reads `map.transform`
|
||||||
|
(public in the MapLibre ~2.4 this plugin targets) to relax the
|
||||||
|
overview's polar pan limit, but MapLibre 6's `Map` no longer exposes
|
||||||
|
`.transform` at all — guarded to skip that (cosmetic) adjustment
|
||||||
|
instead of throwing when it's absent.
|
||||||
|
|
||||||
Offline map text labels need font glyphs too (see
|
Offline map text labels need font glyphs too (see
|
||||||
`docs/architecture.adoc`), vendored under `public/vendor/fonts/` —
|
`docs/architecture.adoc`), vendored under `public/vendor/fonts/` —
|
||||||
SIL Open Font License, not BSD-3-Clause like the above:
|
SIL Open Font License, not BSD-3-Clause like the above:
|
||||||
|
|||||||
@@ -472,6 +472,12 @@ html, body {
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maplibregl-ctrl-overview-map {
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.measure-ctrl {
|
.measure-ctrl {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-8
@@ -14,6 +14,8 @@ import { MeasureControl } from "/js/measure.js";
|
|||||||
import { LAYERS_ICON } from "/js/icons.js";
|
import { LAYERS_ICON } from "/js/icons.js";
|
||||||
import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
|
import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
|
||||||
import HomeButton from "/vendor/npmap-home-button/home-button.esm.js";
|
import HomeButton from "/vendor/npmap-home-button/home-button.esm.js";
|
||||||
|
import OverviewMap from "/vendor/npmap-overview/overview.esm.js";
|
||||||
|
import * as maplibregl from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
||||||
|
|
||||||
const CENTER = [17.1077, 48.1486];
|
const CENTER = [17.1077, 48.1486];
|
||||||
const ZOOM = 13;
|
const ZOOM = 13;
|
||||||
@@ -235,14 +237,18 @@ const map = new MaplibreMap({
|
|||||||
// all grouped together rather than split across corners. Added in this
|
// all grouped together rather than split across corners. Added in this
|
||||||
// order since MapLibre stacks same-position controls in add order.
|
// order since MapLibre stacks same-position controls in add order.
|
||||||
//
|
//
|
||||||
// homePosition/zoom pinned to the app's own default rather than left to the
|
// bounds (rather than homePosition/zoom) so Home resets to an overview of
|
||||||
// button's own fallback (current center/zoom at construction) — now that
|
// the whole country, not back to CENTER/ZOOM's initial Bratislava view —
|
||||||
// the URL hash can restore any view on load (see the Hash option above),
|
// now that the URL hash can restore any view on load (see the Hash option
|
||||||
// "current view" and "the default view" are no longer the same thing, and
|
// above), "current view" and "the default view" are no longer the same
|
||||||
// a home button that resets to wherever you happened to load the page
|
// thing, and a home button that resets to wherever you happened to load
|
||||||
// wouldn't be a home button. This is *not* "go to my location" — that's
|
// the page wouldn't be a home button. This is *not* "go to my location" —
|
||||||
// GeolocateControl, below.
|
// that's GeolocateControl, below.
|
||||||
map.addControl(new HomeButton({ homePosition: CENTER, zoom: ZOOM }), "top-left");
|
const SLOVAKIA_BOUNDS = [
|
||||||
|
[16.83, 47.73],
|
||||||
|
[22.57, 49.61],
|
||||||
|
];
|
||||||
|
map.addControl(new HomeButton({ bounds: SLOVAKIA_BOUNDS }), "top-left");
|
||||||
// showCompass: false — this map never rotates, so a bearing-reset button
|
// showCompass: false — this map never rotates, so a bearing-reset button
|
||||||
// (NavigationControl's default third button) would just be dead weight.
|
// (NavigationControl's default third button) would just be dead weight.
|
||||||
map.addControl(new NavigationControl({ showCompass: false }), "top-left");
|
map.addControl(new NavigationControl({ showCompass: false }), "top-left");
|
||||||
@@ -257,6 +263,21 @@ map.addControl(
|
|||||||
"top-left",
|
"top-left",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// A small inset map showing where the current view sits in context — NPS's
|
||||||
|
// own overview plugin (docs/nps-resources.adoc's "overview" entry, still on
|
||||||
|
// an unmerged branch upstream). It draws its own embedded MapLibre map, so
|
||||||
|
// it needs the maplibregl module itself (not just the Map class) — style
|
||||||
|
// '' (the default) means it clones whatever the main map's current style
|
||||||
|
// is (`this._mainMap.style.serialize()`), so it stays in sync with the
|
||||||
|
// Online/Offline toggle without a second style to maintain here. Built only
|
||||||
|
// once the main map's own style has loaded, matching the plugin's own
|
||||||
|
// example — its internal map is constructed eagerly in the constructor,
|
||||||
|
// before that clone happens in onAdd.
|
||||||
|
map.once("style.load", () => {
|
||||||
|
const OverviewMapControl = OverviewMap(maplibregl);
|
||||||
|
map.addControl(new OverviewMapControl({}), "top-right");
|
||||||
|
});
|
||||||
|
|
||||||
const sidebar = document.getElementById("sidebar");
|
const sidebar = document.getElementById("sidebar");
|
||||||
|
|
||||||
const layerToggle = new LayerToggleControl(applyStyle);
|
const layerToggle = new LayerToggleControl(applyStyle);
|
||||||
|
|||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
### LICENSE.md
|
||||||
|
|
||||||
|
This project constitutes a work of the United States Government and is not subject to domestic copyright protection under [17 USC § 105](https://www.govinfo.gov/app/details/USCODE-2010-title17/USCODE-2010-title17-chap1-sec105).
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
+346
@@ -0,0 +1,346 @@
|
|||||||
|
// Vendored from nationalparkservice/npmap5-plugins, branch
|
||||||
|
// maplibre-gl-overview (not yet merged to main — see
|
||||||
|
// docs/nps-resources.adoc). MIT-licensed per that branch's package.json;
|
||||||
|
// see LICENSE.md alongside this file for the repo's own notice.
|
||||||
|
//
|
||||||
|
// Converted from its UMD dist build (dist/maplibre-gl-overview.min.js) to a
|
||||||
|
// plain ES module export — the only change from upstream besides that
|
||||||
|
// wrapper swap.
|
||||||
|
const blankGeoJsonFeature = () => ({
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": []
|
||||||
|
});
|
||||||
|
const between = (min, value, max) => Math.min(max, Math.max(min, value));
|
||||||
|
function OverviewMap(mapLibrary) {
|
||||||
|
return class OverviewMapControl {
|
||||||
|
constructor(options) {
|
||||||
|
this._extent = blankGeoJsonFeature();
|
||||||
|
this._moving = false;
|
||||||
|
this._easingMain = false;
|
||||||
|
this._keyboardEvents = (e) => {
|
||||||
|
if (e.altKey || e.ctrlKey || e.metaKey)
|
||||||
|
return;
|
||||||
|
// The +1 directions are in Pixel space, not LngLat, in pixel space 0,0 it the top left
|
||||||
|
const events = {
|
||||||
|
"ArrowLeft": () => {
|
||||||
|
e.preventDefault();
|
||||||
|
return { xDir: 1 };
|
||||||
|
},
|
||||||
|
"ArrowRight": () => {
|
||||||
|
e.preventDefault();
|
||||||
|
return { xDir: -1 };
|
||||||
|
},
|
||||||
|
"ArrowUp": () => {
|
||||||
|
e.preventDefault();
|
||||||
|
return { yDir: 1 };
|
||||||
|
},
|
||||||
|
"ArrowDown": () => {
|
||||||
|
e.preventDefault();
|
||||||
|
return { yDir: -1 };
|
||||||
|
},
|
||||||
|
"+": () => ({ zoomDir: +1 }),
|
||||||
|
"-": () => ({ zoomDir: -1 }),
|
||||||
|
};
|
||||||
|
if (events[e.key]) {
|
||||||
|
// Update the variables
|
||||||
|
let { xDir, yDir, zoomDir } = events[e.key]();
|
||||||
|
xDir = xDir || 0;
|
||||||
|
yDir = yDir || 0;
|
||||||
|
zoomDir = zoomDir || 0;
|
||||||
|
if (this._mainMap) {
|
||||||
|
const zoomMain = this._mainMap.getZoom();
|
||||||
|
// Convert the current center of the overview map to pixel space
|
||||||
|
const overviewMapCenterPixels = this._overviewMap.project(this._overviewMap.getCenter());
|
||||||
|
// Get the point that would be panned that center
|
||||||
|
const newCenter = this._overviewMap.unproject(new this._mapLibrary.Point(overviewMapCenterPixels.x + (xDir * this._overviewMap.keyboard._panStep), overviewMapCenterPixels.y + (yDir * this._overviewMap.keyboard._panStep)));
|
||||||
|
// Get the current center of the main map
|
||||||
|
const mainMapCenterPixels = this._mainMap.project(this._mainMap.getCenter());
|
||||||
|
// Get the new point on the main map in pixels
|
||||||
|
const mainMapNewCenterPixels = this._mainMap.project(newCenter);
|
||||||
|
// Define the offset to pan to
|
||||||
|
const xyOffset = {
|
||||||
|
x: mainMapNewCenterPixels.x - mainMapCenterPixels.x,
|
||||||
|
y: mainMapNewCenterPixels.y - mainMapCenterPixels.y
|
||||||
|
};
|
||||||
|
this._mainMap.easeTo({
|
||||||
|
duration: 300,
|
||||||
|
easeId: 'keyboardHandler',
|
||||||
|
easing: (t) => t * (2 - t),
|
||||||
|
zoom: zoomDir ? Math.round(zoomMain) + zoomDir * (e.shiftKey ? 2 : 1) : zoomMain,
|
||||||
|
offset: [xyOffset.x, xyOffset.y],
|
||||||
|
center: this._mainMap.getCenter()
|
||||||
|
}, { originalEvent: e });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Create a container for the overview map
|
||||||
|
this._container = document.createElement("div");
|
||||||
|
this._container.className = "maplibregl-ctrl-overview-map maplibregl-ctrl";
|
||||||
|
this._container.addEventListener("contextmenu", (e) => e.preventDefault());
|
||||||
|
// options
|
||||||
|
const defaultOptions = {
|
||||||
|
zoomLevelOffset: -5,
|
||||||
|
watchEvents: ['move', 'rotate', 'pitch'],
|
||||||
|
overlayPaint: {
|
||||||
|
'fill-color': '#d29700',
|
||||||
|
'fill-opacity': 0.75
|
||||||
|
},
|
||||||
|
selectionPaint: {
|
||||||
|
'fill-color': '#d29700',
|
||||||
|
'fill-opacity': 0.25,
|
||||||
|
'fill-outline-color': '#000000'
|
||||||
|
},
|
||||||
|
width: 150,
|
||||||
|
height: 150,
|
||||||
|
style: '',
|
||||||
|
_keyboardPanStep: 15
|
||||||
|
};
|
||||||
|
this.options = { ...defaultOptions, ...options };
|
||||||
|
this._mapLibrary = mapLibrary;
|
||||||
|
this._container.setAttribute('style', `width: ${this.options.width}px; height: ${this.options.height}px;`);
|
||||||
|
// Create the map for the overview
|
||||||
|
this._overviewMap = new mapLibrary.Map({
|
||||||
|
container: this._container,
|
||||||
|
style: this.options.style,
|
||||||
|
interactive: true,
|
||||||
|
pitchWithRotate: false,
|
||||||
|
attributionControl: false,
|
||||||
|
boxZoom: false,
|
||||||
|
dragRotate: false,
|
||||||
|
touchZoomRotate: false,
|
||||||
|
touchPitch: false,
|
||||||
|
minZoom: -2
|
||||||
|
});
|
||||||
|
this._overviewMap.on('load', () => {
|
||||||
|
// `.transform` existed as a public Map property when this
|
||||||
|
// plugin was built (~MapLibre 2.4.0); this app vendors a
|
||||||
|
// newer MapLibre where it's gone entirely, so this relaxes
|
||||||
|
// nothing and the overview keeps MapLibre's default ~85°
|
||||||
|
// Web Mercator pan limit instead — harmless, just means the
|
||||||
|
// inset can't scroll arbitrarily close to the poles.
|
||||||
|
if (this._overviewMap.transform) {
|
||||||
|
const maxLat = this._overviewMap.transform.maxValidLatitude || 85.051129;
|
||||||
|
this._overviewMap.transform.latRange = [maxLat * -2, maxLat * 2];
|
||||||
|
}
|
||||||
|
this._overviewMap.resize();
|
||||||
|
this._overviewMap.addSource('bboxSource', {
|
||||||
|
'type': 'geojson',
|
||||||
|
'data': this._extent
|
||||||
|
});
|
||||||
|
this._overviewMap.addSource('selectionSource', {
|
||||||
|
'type': 'geojson',
|
||||||
|
'data': blankGeoJsonFeature()
|
||||||
|
});
|
||||||
|
// Add a new layer to visualize the polygon.
|
||||||
|
this._overviewMap.addLayer({
|
||||||
|
'id': 'bboxLayer',
|
||||||
|
'type': 'fill',
|
||||||
|
'source': 'bboxSource',
|
||||||
|
'layout': {},
|
||||||
|
'paint': this.options.overlayPaint
|
||||||
|
});
|
||||||
|
this._overviewMap.addLayer({
|
||||||
|
'id': 'selectionFillLayer',
|
||||||
|
'type': 'fill',
|
||||||
|
'source': 'selectionSource',
|
||||||
|
'layout': {},
|
||||||
|
'paint': this.options.selectionPaint
|
||||||
|
});
|
||||||
|
this._overviewMap.addLayer({
|
||||||
|
'id': 'selectionLineLayer',
|
||||||
|
'type': 'line',
|
||||||
|
'source': 'selectionSource',
|
||||||
|
'layout': {},
|
||||||
|
'paint': {
|
||||||
|
'line-color': this.options.selectionPaint['fill-outline-color'] || '#000000',
|
||||||
|
'line-width': 2
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onAdd(map) {
|
||||||
|
this._mainMap = map;
|
||||||
|
if (this.options.style === '') {
|
||||||
|
this._overviewMap.setStyle(this._mainMap.style.serialize());
|
||||||
|
}
|
||||||
|
// Disable the keyboard events, but use our own
|
||||||
|
this._overviewMap.keyboard.disable();
|
||||||
|
// we still use the _panstep from the keyboard events in out custom function
|
||||||
|
this._overviewMap.keyboard._panStep = this.options._keyboardPanStep;
|
||||||
|
//this._overviewMap.getCanvas().addEventListener('keydown', this._keyboardEvents, true);
|
||||||
|
// Update the overview map when the main map moves
|
||||||
|
this.options.watchEvents.forEach(event => map.on(event, () => this._updateOverview(map)));
|
||||||
|
/////////////////////////////
|
||||||
|
// Update Overview Map triggers
|
||||||
|
// Once the style is loaded, start listening
|
||||||
|
map.once('style.load', () => this._updateOverview(map));
|
||||||
|
// Start listening when it's added
|
||||||
|
this._updateOverview(map);
|
||||||
|
// Update the main map when the overview moves if the map is loaded (otherwise the style.load will pick up the change)
|
||||||
|
this._overviewMap.on('movestart', () => map.loaded() && this._updateMain(map));
|
||||||
|
// Make the overview map take control whenever the viewview map gets clicked
|
||||||
|
const takeControl = () => {
|
||||||
|
map.stop();
|
||||||
|
this._moving = false;
|
||||||
|
this._updateMain(map);
|
||||||
|
};
|
||||||
|
this._overviewMap.on('mousedown', takeControl);
|
||||||
|
this._overviewMap.on('touchstart', takeControl);
|
||||||
|
// Forward keydown events to the main map
|
||||||
|
this._overviewMap.getCanvas().addEventListener('keydown', (e) => {
|
||||||
|
//e.preventDefault();
|
||||||
|
map.stop();
|
||||||
|
this._keyboardEvents(e);
|
||||||
|
});
|
||||||
|
/////////////////////////////
|
||||||
|
return this._container;
|
||||||
|
}
|
||||||
|
onRemove() {
|
||||||
|
if (this._mainMap) {
|
||||||
|
//this._overviewMap.off('movestart', () => this._mainMap && this._moveOverview(this._mainMap));
|
||||||
|
this.options.watchEvents
|
||||||
|
.forEach(event => this._mainMap && this._mainMap
|
||||||
|
.off(event, () => this._updateOverview(this._mainMap)));
|
||||||
|
this._overviewMap.off('movestart', () => this._updateMain(this._mainMap));
|
||||||
|
}
|
||||||
|
if (this.options.style === '') {
|
||||||
|
this._overviewMap.setStyle('');
|
||||||
|
}
|
||||||
|
this._mainMap = undefined;
|
||||||
|
}
|
||||||
|
_updateOverview(map) {
|
||||||
|
var _a;
|
||||||
|
// Don't update the overview map if it's already moving
|
||||||
|
if (this._moving || !this._overviewMap)
|
||||||
|
return;
|
||||||
|
const pixelRatio = map.getPixelRatio();
|
||||||
|
const geojson = blankGeoJsonFeature();
|
||||||
|
if (map) {
|
||||||
|
const center = map.unproject([(map.getCanvas().width / pixelRatio) / 2, (map.getCanvas().height / pixelRatio) / 2]);
|
||||||
|
const bounds = map.getBounds();
|
||||||
|
let extentCoordinates = [
|
||||||
|
[-179.99, bounds.getNorth()],
|
||||||
|
[179.99, bounds.getNorth()],
|
||||||
|
[179.99, bounds.getSouth()],
|
||||||
|
[-179.99, bounds.getSouth()],
|
||||||
|
[-179.99, bounds.getNorth()],
|
||||||
|
];
|
||||||
|
// If the extent isn't larger that the earth, create a better one
|
||||||
|
if ((bounds.getEast() - bounds.getWest()) < 360 + (Math.abs(90 - Math.abs(90 - Math.abs(map.getBearing())))) * (map.getPitch() / 60)) {
|
||||||
|
extentCoordinates = [
|
||||||
|
map.unproject([0, 0]).toArray(),
|
||||||
|
map.unproject([map.getCanvas().width / pixelRatio, 0]).toArray(),
|
||||||
|
map.unproject([map.getCanvas().width / pixelRatio, map.getCanvas().height / pixelRatio]).toArray(),
|
||||||
|
map.unproject([0, map.getCanvas().height / pixelRatio]).toArray(),
|
||||||
|
map.unproject([0, 0]).toArray()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
geojson.features.push({
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": {},
|
||||||
|
"geometry": {
|
||||||
|
"type": "Polygon",
|
||||||
|
"coordinates": [extentCoordinates]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const newOverviewZoom = between(this._overviewMap.getMinZoom(), map.getZoom() + this.options.zoomLevelOffset, this._overviewMap.getMaxZoom());
|
||||||
|
if (!this._easingMain) {
|
||||||
|
this._moving = true;
|
||||||
|
this._overviewMap.setCenter(center);
|
||||||
|
this._overviewMap.setZoom(newOverviewZoom);
|
||||||
|
this._moving = false;
|
||||||
|
}
|
||||||
|
(_a = this._overviewMap.getSource('bboxSource')) === null || _a === void 0 ? void 0 : _a.setData(geojson);
|
||||||
|
this._extent = geojson;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_updateMain(map) {
|
||||||
|
var _a;
|
||||||
|
// Don't update the main map if it's already moving or if it doesn't exist
|
||||||
|
if (this._moving || !this._mainMap)
|
||||||
|
return;
|
||||||
|
const moveStartCenter = this._overviewMap.getCenter();
|
||||||
|
// Take control of the main map
|
||||||
|
this._mainMap.stop();
|
||||||
|
// Get the current extent, and copy it
|
||||||
|
const selectionExtent = JSON.parse(JSON.stringify(this._extent));
|
||||||
|
// Get the coordinates on the screen for the current map extent
|
||||||
|
const screenCoords = (selectionExtent.features[0]).geometry.coordinates[0].map(pos => this._overviewMap.project(pos));
|
||||||
|
// Update the GeoJSON object in the overview map to match the extent
|
||||||
|
(_a = this._overviewMap.getSource('selectionSource')) === null || _a === void 0 ? void 0 : _a.setData(selectionExtent);
|
||||||
|
// Used when overzoomed
|
||||||
|
const overZoomOffset = Math.max(this._overviewMap.getZoom() - this._mainMap.getZoom(), this.options.zoomLevelOffset);
|
||||||
|
// Called when the overview map is moving
|
||||||
|
const moving = () => {
|
||||||
|
var _a;
|
||||||
|
// Gets the current position
|
||||||
|
const currentSelectionExtentCoordinates = screenCoords
|
||||||
|
.map(pos => this._overviewMap.unproject([pos.x, pos.y]))
|
||||||
|
.map(xy => [xy.lng, xy.lat]);
|
||||||
|
// Updates the GeoJSON to match the new position
|
||||||
|
const geojson = blankGeoJsonFeature();
|
||||||
|
geojson.features.push({
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": {},
|
||||||
|
"geometry": {
|
||||||
|
"type": "Polygon",
|
||||||
|
"coordinates": [currentSelectionExtentCoordinates]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Updates the box on the overview map
|
||||||
|
(_a = this._overviewMap.getSource('selectionSource')) === null || _a === void 0 ? void 0 : _a.setData(geojson);
|
||||||
|
};
|
||||||
|
const doneEvents = () => {
|
||||||
|
var _a;
|
||||||
|
// Draw the current box on the overview map
|
||||||
|
(_a = this._overviewMap.getSource('selectionSource')) === null || _a === void 0 ? void 0 : _a.setData(blankGeoJsonFeature());
|
||||||
|
this._moving = false;
|
||||||
|
this._easingMain = false;
|
||||||
|
// Start up the event listeners again
|
||||||
|
this._updateOverview(map);
|
||||||
|
};
|
||||||
|
// Called when the overview map is done moving
|
||||||
|
const moveend = () => {
|
||||||
|
// Stop listening for events
|
||||||
|
this._overviewMap.off('move', moving);
|
||||||
|
this._overviewMap.off('moveend', moveend);
|
||||||
|
this._overviewMap.off('mouseup', moveend);
|
||||||
|
this._overviewMap.off('touchend', moveend);
|
||||||
|
if (this._easingMain)
|
||||||
|
return;
|
||||||
|
// If the map and the oveview map are ready, then we can update the map's center
|
||||||
|
if (this._mainMap) {
|
||||||
|
// Deal with the overview map being outside of its minZoom
|
||||||
|
let newCenter = this._overviewMap.getCenter();
|
||||||
|
const newMainZoom = between(this._mainMap.getMinZoom(), this._overviewMap.getZoom() - overZoomOffset, this._mainMap.getMaxZoom());
|
||||||
|
// Check if the overview map has moved, if so, ease the main map, otherwise finish events
|
||||||
|
if (newCenter.lng !== moveStartCenter.lng || newCenter.lat !== moveStartCenter.lat) {
|
||||||
|
// Execute the "doneEvents" code when the map stops moving
|
||||||
|
this._mainMap.once('moveend', (event) => event.doneEvents && event.doneEvents());
|
||||||
|
this._moving = true;
|
||||||
|
this._easingMain = true;
|
||||||
|
this._mainMap.easeTo({
|
||||||
|
center: newCenter,
|
||||||
|
zoom: newMainZoom
|
||||||
|
}, {
|
||||||
|
doneEvents
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
doneEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._overviewMap.on('move', moving);
|
||||||
|
this._overviewMap.once('moveend', moveend);
|
||||||
|
this._overviewMap.once('mouseup', moveend);
|
||||||
|
this._overviewMap.once('touchend', moveend);
|
||||||
|
}
|
||||||
|
_getCenterAtPitch(map, pitch, rotation) {
|
||||||
|
const pixelRatio = map.getPixelRatio();
|
||||||
|
const center = map.unproject([(map.getCanvas().width / pixelRatio) / 2, (map.getCanvas().height / pixelRatio) / 2]);
|
||||||
|
return center;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export default OverviewMap;
|
||||||
Reference in new issue
Block a user