@goway.to/maps 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/LICENSE +676 -0
- package/NOTICE +25 -0
- package/README.md +59 -2
- package/app.plugin.js +19 -0
- package/dist/CameraBinding.web.d.ts +24 -0
- package/dist/CameraBinding.web.js +46 -0
- package/dist/DefaultMapMarker.d.ts +8 -0
- package/dist/DefaultMapMarker.js +30 -0
- package/dist/GeoJSONSource.d.ts +2 -0
- package/dist/GeoJSONSource.js +17 -0
- package/dist/GeoJSONSource.web.d.ts +2 -0
- package/dist/GeoJSONSource.web.js +68 -0
- package/dist/InteractionBinding.web.d.ts +7 -0
- package/dist/InteractionBinding.web.js +35 -0
- package/dist/Layer.d.ts +2 -0
- package/dist/Layer.js +21 -0
- package/dist/Layer.web.d.ts +2 -0
- package/dist/Layer.web.js +46 -0
- package/dist/LayerStyle.web.d.ts +10 -0
- package/dist/LayerStyle.web.js +45 -0
- package/dist/LightBinding.web.d.ts +5 -0
- package/dist/LightBinding.web.js +19 -0
- package/dist/MapAttribution.d.ts +27 -0
- package/dist/MapAttribution.js +149 -0
- package/dist/MapBrand.d.ts +22 -0
- package/dist/MapBrand.js +91 -0
- package/dist/MapErrorState.d.ts +9 -0
- package/dist/MapErrorState.js +58 -0
- package/dist/MapPlugin.d.ts +6 -0
- package/dist/MapPlugin.js +3 -0
- package/dist/MapPlugin.web.d.ts +2 -0
- package/dist/MapPlugin.web.js +43 -0
- package/dist/MapView.d.ts +2 -0
- package/dist/MapView.js +425 -0
- package/dist/MapView.web.d.ts +5 -0
- package/dist/MapView.web.js +418 -0
- package/dist/Marker.d.ts +13 -0
- package/dist/Marker.js +11 -0
- package/dist/Marker.web.d.ts +15 -0
- package/dist/Marker.web.js +51 -0
- package/dist/Markers.d.ts +2 -0
- package/dist/Markers.js +13 -0
- package/dist/Markers.web.d.ts +2 -0
- package/dist/Markers.web.js +13 -0
- package/dist/PluginProps.web.d.ts +7 -0
- package/dist/PluginProps.web.js +19 -0
- package/dist/RasterDEMSource.d.ts +2 -0
- package/dist/RasterDEMSource.js +7 -0
- package/dist/RasterDEMSource.web.d.ts +2 -0
- package/dist/RasterDEMSource.web.js +44 -0
- package/dist/SourceData.web.d.ts +6 -0
- package/dist/SourceData.web.js +14 -0
- package/dist/UserLocation.d.ts +2 -0
- package/dist/UserLocation.js +19 -0
- package/dist/UserLocation.web.d.ts +2 -0
- package/dist/UserLocation.web.js +21 -0
- package/dist/brand/BrandedChrome.d.ts +47 -0
- package/dist/brand/BrandedChrome.js +51 -0
- package/dist/brand/GowayLogo.d.ts +28 -0
- package/dist/brand/GowayLogo.js +49 -0
- package/dist/brand/artwork.d.ts +130 -0
- package/dist/brand/artwork.js +176 -0
- package/dist/brand/logoAccessibility.d.ts +26 -0
- package/dist/brand/logoAccessibility.js +7 -0
- package/dist/cameraReports.web.d.ts +50 -0
- package/dist/cameraReports.web.js +117 -0
- package/dist/cameraTarget.d.ts +14 -0
- package/dist/cameraTarget.js +8 -0
- package/dist/chromeLift.d.ts +15 -0
- package/dist/chromeLift.js +15 -0
- package/dist/clusters.d.ts +57 -0
- package/dist/clusters.js +102 -0
- package/dist/commands.web.d.ts +16 -0
- package/dist/commands.web.js +158 -0
- package/dist/compactCredit.d.ts +52 -0
- package/dist/compactCredit.js +40 -0
- package/dist/creditHeight.d.ts +3 -0
- package/dist/creditHeight.js +12 -0
- package/dist/creditLabels.d.ts +30 -0
- package/dist/creditLabels.js +30 -0
- package/dist/creditRise.d.ts +28 -0
- package/dist/creditRise.js +33 -0
- package/dist/dragAxes.web.d.ts +65 -0
- package/dist/dragAxes.web.js +70 -0
- package/dist/engineContext.web.d.ts +13 -0
- package/dist/engineContext.web.js +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/index.web.d.ts +13 -0
- package/dist/index.web.js +13 -0
- package/dist/labels.d.ts +202 -0
- package/dist/labels.js +451 -0
- package/dist/loadedCamera.d.ts +64 -0
- package/dist/loadedCamera.js +56 -0
- package/dist/locationStore.d.ts +16 -0
- package/dist/locationStore.js +132 -0
- package/dist/mapRef.d.ts +4 -0
- package/dist/mapRef.js +13 -0
- package/dist/moveMarkers.d.ts +27 -0
- package/dist/moveMarkers.js +22 -0
- package/dist/nativeCameraReports.d.ts +44 -0
- package/dist/nativeCameraReports.js +73 -0
- package/dist/plugin.d.ts +63 -0
- package/dist/plugin.js +2 -0
- package/dist/queryLayers.d.ts +19 -0
- package/dist/queryLayers.js +54 -0
- package/dist/shared-exports.d.ts +17 -0
- package/dist/shared-exports.js +13 -0
- package/dist/shared.d.ts +170 -0
- package/dist/shared.js +337 -0
- package/dist/sourceContext.d.ts +2 -0
- package/dist/sourceContext.js +9 -0
- package/dist/styleUrl.d.ts +19 -0
- package/dist/styleUrl.js +22 -0
- package/dist/testing.d.ts +41 -0
- package/dist/testing.js +80 -0
- package/dist/types.d.ts +348 -0
- package/dist/types.js +1 -0
- package/dist/useMarkerSlots.d.ts +6 -0
- package/dist/useMarkerSlots.js +56 -0
- package/dist/useUserLocation.d.ts +44 -0
- package/dist/useUserLocation.js +247 -0
- package/dist/zoom.d.ts +2 -0
- package/dist/zoom.js +10 -0
- package/metro.cjs +70 -0
- package/package.json +115 -4
package/NOTICE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
============================================================================
|
|
2
|
+
NOTICE
|
|
3
|
+
OxyHQ/GoWay — the @goway.to/sdk package
|
|
4
|
+
Copyright (c) 2026-present The Oxy Collective, Inc.
|
|
5
|
+
============================================================================
|
|
6
|
+
|
|
7
|
+
This product is licensed under the Breathe License 1.0, license identifier
|
|
8
|
+
LicenseRef-Breathe-1.0. See the LICENSE file. Commercial terms are available
|
|
9
|
+
at https://github.com/OxyHQ/.github/blob/main/LICENSE-COMMERCIAL.md
|
|
10
|
+
|
|
11
|
+
Attribution under Section 3.1 of the Breathe License is required of
|
|
12
|
+
everyone, including paying commercial licensees, and cannot be waived.
|
|
13
|
+
|
|
14
|
+
----------------------------------------------------------------------------
|
|
15
|
+
THIRD PARTY COMPONENTS
|
|
16
|
+
----------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
This repository contains no vendored third party source. Its dependencies
|
|
19
|
+
are resolved and installed by the package manager and remain governed by
|
|
20
|
+
their own licenses.
|
|
21
|
+
|
|
22
|
+
This work must not take on a GPL or AGPL dependency that is linked in
|
|
23
|
+
process: the Breathe License is not compatible with either, in either
|
|
24
|
+
direction. If you are adding one, stop and read
|
|
25
|
+
https://github.com/OxyHQ/.github/blob/main/licensing/README.md
|
package/README.md
CHANGED
|
@@ -1,3 +1,60 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@goway.to/maps`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
GoWay's maps SDK for React and React Native: one declarative map for the web
|
|
4
|
+
(MapLibre GL JS) and iOS/Android (MapLibre Native), drawn in GoWay's styles.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
bun add @goway.to/maps @goway.to/sdk maplibre-gl @maplibre/maplibre-react-native
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { MapView, Markers, UserLocation, type MapViewRef } from '@goway.to/maps';
|
|
12
|
+
|
|
13
|
+
<MapView
|
|
14
|
+
ref={mapRef}
|
|
15
|
+
mapStyle="goway://styles/standard" // the default; day in light mode, night in dark
|
|
16
|
+
camera={{ bounds: searchedArea }} // framed whenever its VALUE changes
|
|
17
|
+
onCameraChanged={({ bounds, source, isFinal }) => …}
|
|
18
|
+
onPress={({ coordinate, label }) => …}
|
|
19
|
+
>
|
|
20
|
+
<Markers data={homes} selectedId={selected} cluster onPress={(home) => …} />
|
|
21
|
+
<UserLocation />
|
|
22
|
+
</MapView>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- **Markers** are Bloom's (`@oxy.so/bloom/map-marker`) on both platforms, and
|
|
26
|
+
cluster with Mapbox's source options (`cluster`, `clusterRadius`,
|
|
27
|
+
`clusterMaxZoom`).
|
|
28
|
+
- **The camera** opens on `initialViewState` and follows `camera` by value, so
|
|
29
|
+
a re-render never moves it under a reader who has panned; the ref
|
|
30
|
+
(`fitBounds`, `moveTo`, …) is for event handlers.
|
|
31
|
+
- **User location** is drawn only against a permission already granted;
|
|
32
|
+
`useUserLocation()` asks, from a tap.
|
|
33
|
+
- Every map carries the GoWay mark and the data credit, with no prop to turn
|
|
34
|
+
either off.
|
|
35
|
+
|
|
36
|
+
## Setup
|
|
37
|
+
|
|
38
|
+
- **Native**: add the config plugin (`plugins: ['@goway.to/maps']` in
|
|
39
|
+
`app.config.js`) and make a development or store build — MapLibre Native is
|
|
40
|
+
not in Expo Go.
|
|
41
|
+
- **Web with Metro (Expo)**: wrap the Metro config, which serves MapLibre's
|
|
42
|
+
worker from your origin (`maplibre-gl` 6 starts it by URL), and gitignore
|
|
43
|
+
`public/vendor/`:
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
const { withGoWayMaps } = require('@goway.to/maps/metro');
|
|
47
|
+
module.exports = withGoWayMaps(getDefaultConfig(__dirname));
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- Render inside Bloom's `BloomProvider`: the map follows its theme and locale.
|
|
51
|
+
|
|
52
|
+
## Entry points
|
|
53
|
+
|
|
54
|
+
| import | what |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `@goway.to/maps` | `MapView`, `Markers`, `GeoJSONSource`, `RasterDEMSource`, `Layer`, `UserLocation`, `MapPlugin`, `useMap`, `useUserLocation` |
|
|
57
|
+
| `@goway.to/maps/plugin` | the engine-plugin protocol and the guards every coordinate crossing into an engine goes through (no React) |
|
|
58
|
+
| `@goway.to/maps/testing` | a fake engine that drives the map's real camera reports, for testing a screen's camera logic |
|
|
59
|
+
| `@goway.to/maps/brand` | the GoWay mark as geometry |
|
|
60
|
+
| `@goway.to/maps/metro`, `@goway.to/maps/app.plugin.js` | the Metro helper and the Expo config plugin |
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@goway.to/maps` Expo config plugin: adds MapLibre Native to a development
|
|
3
|
+
* or store build (it is absent from Expo Go).
|
|
4
|
+
*
|
|
5
|
+
* // app.config.js
|
|
6
|
+
* plugins: ['@goway.to/maps']
|
|
7
|
+
*
|
|
8
|
+
* It applies `@maplibre/maplibre-react-native`'s own plugin, so an app names
|
|
9
|
+
* one map package in its config, as with `@rnmapbox/maps`. The location usage
|
|
10
|
+
* strings stay the app's (`expo-location`'s plugin): only the app knows why it
|
|
11
|
+
* asks.
|
|
12
|
+
*/
|
|
13
|
+
// The package exports its plugin as an ES module's default.
|
|
14
|
+
const loaded = require('@maplibre/maplibre-react-native/app.plugin.js');
|
|
15
|
+
const maplibre = typeof loaded === 'function' ? loaded : loaded.default;
|
|
16
|
+
|
|
17
|
+
module.exports = function withGoWayMaps(config, options = {}) {
|
|
18
|
+
return maplibre(config, options);
|
|
19
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `MapView`'s `camera` target, web — framed whenever its VALUE changes.
|
|
3
|
+
*
|
|
4
|
+
* The camera is an imperative object behind maplibre-gl's `fitBounds`, and a
|
|
5
|
+
* target that changes because the app's state changed — a place picked, a URL
|
|
6
|
+
* arriving, back and forward — comes with no event this map handles. So the
|
|
7
|
+
* one binding below applies it, keyed on the four numbers of the box (or the
|
|
8
|
+
* point and zoom), never on the object: a re-render with an equal target does
|
|
9
|
+
* nothing, and the map never moves under a reader who has panned.
|
|
10
|
+
*
|
|
11
|
+
* The first target is JUMPED to — the map opens on it. A later change
|
|
12
|
+
* animates over `duration`. An engine rebuilt for a new style keeps the
|
|
13
|
+
* camera it had, so an unchanged target is not applied to it again: changing
|
|
14
|
+
* the map's look never moves the map.
|
|
15
|
+
*/
|
|
16
|
+
import { type RefObject } from 'react';
|
|
17
|
+
import type { Map as MapLibreMap } from 'maplibre-gl';
|
|
18
|
+
import type { MapCameraTarget } from './types';
|
|
19
|
+
export declare function CameraBinding({ map, target, applied, }: {
|
|
20
|
+
map: MapLibreMap;
|
|
21
|
+
target: MapCameraTarget;
|
|
22
|
+
/** The target last applied, on ANY engine of this map — owned by `MapView`, which outlives them. */
|
|
23
|
+
applied: RefObject<string | null>;
|
|
24
|
+
}): null;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `MapView`'s `camera` target, web — framed whenever its VALUE changes.
|
|
3
|
+
*
|
|
4
|
+
* The camera is an imperative object behind maplibre-gl's `fitBounds`, and a
|
|
5
|
+
* target that changes because the app's state changed — a place picked, a URL
|
|
6
|
+
* arriving, back and forward — comes with no event this map handles. So the
|
|
7
|
+
* one binding below applies it, keyed on the four numbers of the box (or the
|
|
8
|
+
* point and zoom), never on the object: a re-render with an equal target does
|
|
9
|
+
* nothing, and the map never moves under a reader who has panned.
|
|
10
|
+
*
|
|
11
|
+
* The first target is JUMPED to — the map opens on it. A later change
|
|
12
|
+
* animates over `duration`. An engine rebuilt for a new style keeps the
|
|
13
|
+
* camera it had, so an unchanged target is not applied to it again: changing
|
|
14
|
+
* the map's look never moves the map.
|
|
15
|
+
*/
|
|
16
|
+
import { useLayoutEffect } from 'react';
|
|
17
|
+
import { cameraTargetKey } from './cameraTarget';
|
|
18
|
+
import { createWebMapApi } from './commands.web';
|
|
19
|
+
export function CameraBinding({ map, target, applied, }) {
|
|
20
|
+
const { bounds, center, zoom, padding, maxZoom, duration } = target;
|
|
21
|
+
const key = cameraTargetKey({ bounds, center, zoom });
|
|
22
|
+
// Binding: the engine's camera follows the VALUE of `camera` (`key`).
|
|
23
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: `key` is the value the target stands for; a new object with the same numbers must not move the camera
|
|
24
|
+
useLayoutEffect(() => {
|
|
25
|
+
if (key === null || key === applied.current)
|
|
26
|
+
return;
|
|
27
|
+
const first = applied.current === null;
|
|
28
|
+
applied.current = key;
|
|
29
|
+
const api = createWebMapApi(() => map);
|
|
30
|
+
const time = first ? 0 : duration;
|
|
31
|
+
if (bounds) {
|
|
32
|
+
api.fitBounds(bounds, {
|
|
33
|
+
...(padding !== undefined ? { padding } : {}),
|
|
34
|
+
...(maxZoom !== undefined ? { maxZoom } : {}),
|
|
35
|
+
...(time !== undefined ? { duration: time } : {}),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else if (center) {
|
|
39
|
+
api.moveTo(center, {
|
|
40
|
+
...(zoom !== undefined ? { zoom } : {}),
|
|
41
|
+
...(time !== undefined ? { duration: time } : {}),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}, [map, key]);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MapMarkerItem } from './types';
|
|
2
|
+
export interface DefaultMapMarkerProps {
|
|
3
|
+
item: MapMarkerItem;
|
|
4
|
+
onPress?: () => void;
|
|
5
|
+
}
|
|
6
|
+
declare function DefaultMapMarkerComponent({ item, onPress }: DefaultMapMarkerProps): import("react").JSX.Element;
|
|
7
|
+
export declare const DefaultMapMarker: import("react").MemoExoticComponent<typeof DefaultMapMarkerComponent>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The marker body both forks draw when a caller does not pass `renderMarker`.
|
|
4
|
+
*
|
|
5
|
+
* These are Bloom components, not GoWay ones: `@oxy.so/bloom/map-marker`
|
|
6
|
+
* already ships the pill and the cluster bubble with their `default` /
|
|
7
|
+
* `active` / `visited` states, their press affordance and their web focus
|
|
8
|
+
* ring, so a local marker would be a second, worse copy that drifts from the
|
|
9
|
+
* design system at the first token change.
|
|
10
|
+
*
|
|
11
|
+
* Because it is ordinary Bloom UI on both platforms (native positions it with
|
|
12
|
+
* MapLibre's `<Marker>`, web with a DOM `maplibregl.Marker` it is portalled
|
|
13
|
+
* into), a marker looks identical on iOS, Android and the web.
|
|
14
|
+
*/
|
|
15
|
+
import { memo } from 'react';
|
|
16
|
+
import { MapClusterMarker, MapPriceMarker } from '@oxy.so/bloom/map-marker';
|
|
17
|
+
function DefaultMapMarkerComponent({ item, onPress }) {
|
|
18
|
+
if (item.kind === 'cluster') {
|
|
19
|
+
return (_jsx(MapClusterMarker, { count: item.cluster.count, state: item.state, onPress: onPress, testID: `map-cluster-${item.cluster.id}` }));
|
|
20
|
+
}
|
|
21
|
+
const { marker } = item;
|
|
22
|
+
return (_jsx(MapPriceMarker
|
|
23
|
+
// Bloom's pill is a labelled marker; the label happens to be a price in
|
|
24
|
+
// its first consumer. GoWay passes a place name, a category or nothing.
|
|
25
|
+
, {
|
|
26
|
+
// Bloom's pill is a labelled marker; the label happens to be a price in
|
|
27
|
+
// its first consumer. GoWay passes a place name, a category or nothing.
|
|
28
|
+
price: marker.label ?? '•', state: item.state, size: marker.label ? 'default' : 'compact', onPress: onPress, accessibilityLabel: marker.accessibilityLabel ?? marker.label ?? 'Map marker', testID: `map-marker-${marker.id}` }));
|
|
29
|
+
}
|
|
30
|
+
export const DefaultMapMarker = memo(DefaultMapMarkerComponent);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { GeoJSONSource as NativeGeoJSONSource } from '@maplibre/maplibre-react-native';
|
|
3
|
+
import { isDrawableGeoJSON, reportMapDefect } from './shared';
|
|
4
|
+
import { SourceIdContext } from './sourceContext';
|
|
5
|
+
const EMPTY = { type: 'FeatureCollection', features: [] };
|
|
6
|
+
export function GeoJSONSource({ id, data, cluster, clusterRadius, clusterMaxZoom, clusterMinPoints, clusterProperties, children, }) {
|
|
7
|
+
// Raw GeoJSON fails SILENTLY rather than loudly: refused here, and said so.
|
|
8
|
+
const drawable = isDrawableGeoJSON(data);
|
|
9
|
+
if (!drawable) {
|
|
10
|
+
reportMapDefect(`source:${id}`, `Dropped the data of source "${id}": it is not drawable GeoJSON.`);
|
|
11
|
+
}
|
|
12
|
+
return (_jsx(NativeGeoJSONSource, { id: id, data: drawable ? data : EMPTY, ...(cluster ? { cluster: true } : {}), ...(clusterRadius !== undefined ? { clusterRadius } : {}), ...(clusterMaxZoom !== undefined ? { clusterMaxZoom } : {}), ...(clusterMinPoints !== undefined ? { clusterMinPoints } : {}), ...(clusterProperties
|
|
13
|
+
? {
|
|
14
|
+
clusterProperties: clusterProperties,
|
|
15
|
+
}
|
|
16
|
+
: {}), children: _jsx(SourceIdContext.Provider, { value: id, children: children }) }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `<GeoJSONSource>`, web — a GeoJSON source on the engine, with `<Layer>`
|
|
4
|
+
* children drawing it.
|
|
5
|
+
*
|
|
6
|
+
* Two bindings, in two files, because a source has two lives: it is ADDED
|
|
7
|
+
* once per engine (here, with its clustering options, which maplibre-gl takes
|
|
8
|
+
* only at creation) and its DATA changes many times (`SourceData.web.tsx`,
|
|
9
|
+
* `setData`). Removing and re-adding it for every new data set would take its
|
|
10
|
+
* layers with it.
|
|
11
|
+
*
|
|
12
|
+
* The layers render only once the source exists: React binds children before
|
|
13
|
+
* their parent, and a layer added before its source throws.
|
|
14
|
+
*/
|
|
15
|
+
import { useContext, useLayoutEffect, useState } from 'react';
|
|
16
|
+
import { WebEngineContext } from './engineContext.web';
|
|
17
|
+
import { isDrawableGeoJSON, reportMapDefect, runEngineCommand } from './shared';
|
|
18
|
+
import { SourceIdContext } from './sourceContext';
|
|
19
|
+
import { SourceData } from './SourceData.web';
|
|
20
|
+
const EMPTY = { type: 'FeatureCollection', features: [] };
|
|
21
|
+
export function GeoJSONSource(props) {
|
|
22
|
+
const engine = useContext(WebEngineContext);
|
|
23
|
+
return engine ? _jsx(GeoJSONSourceBinding, { map: engine.map, ...props }) : null;
|
|
24
|
+
}
|
|
25
|
+
function GeoJSONSourceBinding({ map, id, data, cluster, clusterRadius, clusterMaxZoom, clusterMinPoints, clusterProperties, children, }) {
|
|
26
|
+
const [added, setAdded] = useState(null);
|
|
27
|
+
// Binding: the source `id` exists on the engine, with its creation options.
|
|
28
|
+
useLayoutEffect(() => {
|
|
29
|
+
const spec = {
|
|
30
|
+
type: 'geojson',
|
|
31
|
+
data: EMPTY,
|
|
32
|
+
...(cluster ? { cluster: true } : {}),
|
|
33
|
+
...(clusterRadius !== undefined ? { clusterRadius } : {}),
|
|
34
|
+
...(clusterMaxZoom !== undefined ? { clusterMaxZoom } : {}),
|
|
35
|
+
...(clusterMinPoints !== undefined ? { clusterMinPoints } : {}),
|
|
36
|
+
...(clusterProperties
|
|
37
|
+
? {
|
|
38
|
+
clusterProperties: clusterProperties,
|
|
39
|
+
}
|
|
40
|
+
: {}),
|
|
41
|
+
};
|
|
42
|
+
if (!runEngineCommand('addSource', () => map.addSource(id, spec)))
|
|
43
|
+
return;
|
|
44
|
+
setAdded(map);
|
|
45
|
+
return () => {
|
|
46
|
+
setAdded(null);
|
|
47
|
+
runEngineCommand('removeSource', () => {
|
|
48
|
+
// A layer still drawing the source would make the removal throw.
|
|
49
|
+
for (const layer of map.getStyle()?.layers ?? []) {
|
|
50
|
+
if ('source' in layer && layer.source === id)
|
|
51
|
+
map.removeLayer(layer.id);
|
|
52
|
+
}
|
|
53
|
+
if (map.getSource(id))
|
|
54
|
+
map.removeSource(id);
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
}, [map, id, cluster, clusterRadius, clusterMaxZoom, clusterMinPoints, clusterProperties]);
|
|
58
|
+
if (added !== map)
|
|
59
|
+
return null;
|
|
60
|
+
// Raw GeoJSON fails SILENTLY rather than loudly: a NaN vertex tiles to
|
|
61
|
+
// nothing and the line the reader asked for is simply absent. Refused here,
|
|
62
|
+
// and said so.
|
|
63
|
+
const drawable = isDrawableGeoJSON(data);
|
|
64
|
+
if (!drawable) {
|
|
65
|
+
reportMapDefect(`source:${id}`, `Dropped the data of source "${id}": it is not drawable GeoJSON.`);
|
|
66
|
+
}
|
|
67
|
+
return (_jsxs(SourceIdContext.Provider, { value: id, children: [_jsx(SourceData, { map: map, id: id, data: drawable ? data : EMPTY }), children] }));
|
|
68
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Map as MapLibreMap } from 'maplibre-gl';
|
|
2
|
+
import type { MapInteractionOptions } from './types';
|
|
3
|
+
export declare function InteractionBinding({ map, interaction, maxPitch, }: {
|
|
4
|
+
map: MapLibreMap;
|
|
5
|
+
interaction: MapInteractionOptions | undefined;
|
|
6
|
+
maxPitch: number;
|
|
7
|
+
}): null;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine's gesture handlers and pitch ceiling, bound to `interaction` and
|
|
3
|
+
* `maxPitch`.
|
|
4
|
+
*
|
|
5
|
+
* maplibre-gl toggles gestures with imperative `enable()`/`disable()` calls on
|
|
6
|
+
* its handlers, so this component holds the one binding that keeps them equal
|
|
7
|
+
* to the props. Every flag is independent, on web as on native: `rotate` and
|
|
8
|
+
* `pitch` are separate axes even though MapLibre drives both from one desktop
|
|
9
|
+
* handler (`dragAxes.web.ts`).
|
|
10
|
+
*/
|
|
11
|
+
import { useLayoutEffect } from 'react';
|
|
12
|
+
import { applyDragAxes } from './dragAxes.web';
|
|
13
|
+
import { resolveInteraction, runEngineCommand } from './shared';
|
|
14
|
+
export function InteractionBinding({ map, interaction, maxPitch, }) {
|
|
15
|
+
const { pan, zoom, rotate, pitch } = resolveInteraction(interaction);
|
|
16
|
+
// Binding: the engine's gesture handlers follow `interaction` and `maxPitch`.
|
|
17
|
+
useLayoutEffect(() => {
|
|
18
|
+
const toggle = (handler, on) => on ? handler.enable() : handler.disable();
|
|
19
|
+
runEngineCommand('interaction', () => {
|
|
20
|
+
toggle(map.dragPan, pan);
|
|
21
|
+
toggle(map.scrollZoom, zoom);
|
|
22
|
+
toggle(map.touchZoomRotate, zoom);
|
|
23
|
+
toggle(map.doubleClickZoom, zoom);
|
|
24
|
+
if (rotate)
|
|
25
|
+
map.touchZoomRotate.enableRotation();
|
|
26
|
+
else
|
|
27
|
+
map.touchZoomRotate.disableRotation();
|
|
28
|
+
applyDragAxes(map.dragRotate, { rotate, pitch });
|
|
29
|
+
toggle(map.touchPitch, pitch);
|
|
30
|
+
// The backstop: with pitch off no path can tilt the camera.
|
|
31
|
+
map.setMaxPitch(pitch ? maxPitch : 0);
|
|
32
|
+
});
|
|
33
|
+
}, [map, pan, zoom, rotate, pitch, maxPitch]);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
package/dist/Layer.d.ts
ADDED
package/dist/Layer.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Layer as NativeLayer } from '@maplibre/maplibre-react-native';
|
|
3
|
+
import { useSourceId } from './sourceContext';
|
|
4
|
+
export function Layer({ id, type, paint, layout, filter, beforeId, minzoom, maxzoom }) {
|
|
5
|
+
const source = useSourceId();
|
|
6
|
+
const props = {
|
|
7
|
+
id,
|
|
8
|
+
type,
|
|
9
|
+
source,
|
|
10
|
+
...(paint ? { paint } : {}),
|
|
11
|
+
...(layout ? { layout } : {}),
|
|
12
|
+
...(filter ? { filter } : {}),
|
|
13
|
+
...(beforeId ? { beforeId } : {}),
|
|
14
|
+
...(minzoom !== undefined ? { minzoom } : {}),
|
|
15
|
+
...(maxzoom !== undefined ? { maxzoom } : {}),
|
|
16
|
+
};
|
|
17
|
+
// MapLibre Native types a layer as a union keyed by `type`; a `<Layer>`
|
|
18
|
+
// takes its type as a value and its properties as plain style-spec names,
|
|
19
|
+
// which the engine validates itself.
|
|
20
|
+
return _jsx(NativeLayer, { ...props });
|
|
21
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `<Layer>`, web — a style layer drawing the source around it.
|
|
4
|
+
*
|
|
5
|
+
* Added once per engine with what maplibre-gl fixes at creation (its id,
|
|
6
|
+
* type, source and place in the stack); its paint, layout and filter are
|
|
7
|
+
* their own binding (`LayerStyle.web.tsx`), so a new colour restyles the layer
|
|
8
|
+
* instead of removing and re-adding it.
|
|
9
|
+
*/
|
|
10
|
+
import { useContext, useLayoutEffect, useState } from 'react';
|
|
11
|
+
import { WebEngineContext } from './engineContext.web';
|
|
12
|
+
import { LayerStyle } from './LayerStyle.web';
|
|
13
|
+
import { runEngineCommand } from './shared';
|
|
14
|
+
import { useSourceId } from './sourceContext';
|
|
15
|
+
export function Layer(props) {
|
|
16
|
+
const engine = useContext(WebEngineContext);
|
|
17
|
+
const source = useSourceId();
|
|
18
|
+
return engine ? _jsx(LayerBinding, { map: engine.map, source: source, ...props }) : null;
|
|
19
|
+
}
|
|
20
|
+
function LayerBinding({ map, source, id, type, beforeId, minzoom, maxzoom, paint, layout, filter, }) {
|
|
21
|
+
const [added, setAdded] = useState(null);
|
|
22
|
+
// Binding: the layer `id` exists on the engine, over `source`, under `beforeId`.
|
|
23
|
+
useLayoutEffect(() => {
|
|
24
|
+
const spec = {
|
|
25
|
+
id,
|
|
26
|
+
type,
|
|
27
|
+
source,
|
|
28
|
+
...(minzoom !== undefined ? { minzoom } : {}),
|
|
29
|
+
...(maxzoom !== undefined ? { maxzoom } : {}),
|
|
30
|
+
};
|
|
31
|
+
// Under `beforeId` only when the style has it: a style that makes no such
|
|
32
|
+
// promise gets the layer on top rather than an exception.
|
|
33
|
+
const before = beforeId && map.getLayer(beforeId) ? beforeId : undefined;
|
|
34
|
+
if (!runEngineCommand('addLayer', () => map.addLayer(spec, before)))
|
|
35
|
+
return;
|
|
36
|
+
setAdded(map);
|
|
37
|
+
return () => {
|
|
38
|
+
setAdded(null);
|
|
39
|
+
runEngineCommand('removeLayer', () => {
|
|
40
|
+
if (map.getLayer(id))
|
|
41
|
+
map.removeLayer(id);
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
}, [map, source, id, type, beforeId, minzoom, maxzoom]);
|
|
45
|
+
return added === map ? (_jsx(LayerStyle, { map: map, id: id, paint: paint, layout: layout, filter: filter })) : null;
|
|
46
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Map as MapLibreMap } from 'maplibre-gl';
|
|
2
|
+
type Properties = Readonly<Record<string, unknown>>;
|
|
3
|
+
export declare function LayerStyle({ map, id, paint, layout, filter, }: {
|
|
4
|
+
map: MapLibreMap;
|
|
5
|
+
id: string;
|
|
6
|
+
paint?: Properties;
|
|
7
|
+
layout?: Properties;
|
|
8
|
+
filter?: readonly unknown[];
|
|
9
|
+
}): null;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A layer's paint, layout and filter, bound to its props with maplibre-gl's
|
|
3
|
+
* imperative setters (see `Layer.web.tsx` for why it is its own binding).
|
|
4
|
+
* A property the props drop is reset to the style default.
|
|
5
|
+
*/
|
|
6
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
7
|
+
import { runEngineCommand } from './shared';
|
|
8
|
+
const NONE = {};
|
|
9
|
+
export function LayerStyle({ map, id, paint = NONE, layout = NONE, filter, }) {
|
|
10
|
+
// What this binding last set on THIS engine's layer; a rebuilt engine's
|
|
11
|
+
// layer starts from the style's defaults.
|
|
12
|
+
const applied = useRef({
|
|
13
|
+
map: null,
|
|
14
|
+
paint: NONE,
|
|
15
|
+
layout: NONE,
|
|
16
|
+
});
|
|
17
|
+
// Binding: the layer's paint, layout and filter follow the props.
|
|
18
|
+
useLayoutEffect(() => {
|
|
19
|
+
const previous = applied.current.map === map ? applied.current : { map, paint: NONE, layout: NONE };
|
|
20
|
+
runEngineCommand('setLayerStyle', () => {
|
|
21
|
+
for (const key of new Set([...Object.keys(previous.paint), ...Object.keys(paint)])) {
|
|
22
|
+
if (previous.paint[key] !== paint[key])
|
|
23
|
+
setPaint(map, id, key, paint[key]);
|
|
24
|
+
}
|
|
25
|
+
for (const key of new Set([...Object.keys(previous.layout), ...Object.keys(layout)])) {
|
|
26
|
+
if (previous.layout[key] !== layout[key])
|
|
27
|
+
setLayout(map, id, key, layout[key]);
|
|
28
|
+
}
|
|
29
|
+
map.setFilter(id, (filter ?? null));
|
|
30
|
+
});
|
|
31
|
+
applied.current = { map, paint, layout };
|
|
32
|
+
}, [map, id, paint, layout, filter]);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* maplibre-gl types a property NAME by the layer type it belongs to; a
|
|
37
|
+
* `<Layer>` names its properties in plain style-spec strings, which the
|
|
38
|
+
* engine validates itself (an unknown name is reported, not thrown).
|
|
39
|
+
*/
|
|
40
|
+
function setPaint(map, id, name, value) {
|
|
41
|
+
map.setPaintProperty.call(map, id, name, value);
|
|
42
|
+
}
|
|
43
|
+
function setLayout(map, id, name, value) {
|
|
44
|
+
map.setLayoutProperty.call(map, id, name, value);
|
|
45
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The style's light, bound to the `light` prop.
|
|
3
|
+
*
|
|
4
|
+
* A light is a property of the loaded style, and maplibre-gl sets it with an
|
|
5
|
+
* imperative `setLight` — so this component holds the one binding that keeps
|
|
6
|
+
* the engine's light equal to the prop. `null` leaves the document's own
|
|
7
|
+
* light alone. maplibre-gl diffs the light itself and eases the change.
|
|
8
|
+
*/
|
|
9
|
+
import { useLayoutEffect } from 'react';
|
|
10
|
+
import { runEngineCommand } from './shared';
|
|
11
|
+
export function LightBinding({ map, light, }) {
|
|
12
|
+
// Binding: the engine's light follows `light` (an imperative style setter).
|
|
13
|
+
useLayoutEffect(() => {
|
|
14
|
+
if (!light)
|
|
15
|
+
return;
|
|
16
|
+
runEngineCommand('setLight', () => map.setLight(light));
|
|
17
|
+
}, [map, light]);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { MAP_CREDIT_CLEARANCE } from './creditRise';
|
|
2
|
+
import type { MapCreditDisclosure, MapCreditLink } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* How much of the bottom-right the credit occupies above the bottom edge when
|
|
5
|
+
* it is one line (`creditRise.ts`). Bottom-right chrome a screen floats there
|
|
6
|
+
* starts above it and rises by the credit's measured extra height
|
|
7
|
+
* (`creditHeight.ts`), so the two never overlap.
|
|
8
|
+
*/
|
|
9
|
+
export { MAP_CREDIT_CLEARANCE };
|
|
10
|
+
/**
|
|
11
|
+
* The credit GoWay's tiles oblige: OpenMapTiles' schema and OpenStreetMap's
|
|
12
|
+
* data. Always shown, before anything an app adds.
|
|
13
|
+
*/
|
|
14
|
+
export declare const MAP_DATA_CREDITS: readonly MapCreditLink[];
|
|
15
|
+
/**
|
|
16
|
+
* @param extra - credits owed while something the app draws is on screen,
|
|
17
|
+
* after the basemap's own.
|
|
18
|
+
*/
|
|
19
|
+
declare function MapAttributionComponent({ extra, disclosure, expanded, onToggle, }: {
|
|
20
|
+
extra?: readonly MapCreditLink[];
|
|
21
|
+
/** A line the credit carries while something it names is drawn, `null` otherwise. */
|
|
22
|
+
disclosure?: MapCreditDisclosure | null;
|
|
23
|
+
/** Spelled out, or folded into the ⓘ (`compactCredit.ts`). */
|
|
24
|
+
expanded: boolean;
|
|
25
|
+
onToggle: () => void;
|
|
26
|
+
}): import("react").JSX.Element;
|
|
27
|
+
export declare const MapAttribution: import("react").MemoExoticComponent<typeof MapAttributionComponent>;
|