map-zero 0.1.0
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 +13 -0
- package/LICENSE +21 -0
- package/README.md +220 -0
- package/docs/api.md +66 -0
- package/docs/architecture.md +87 -0
- package/docs/cartography.md +77 -0
- package/docs/cesium.md +107 -0
- package/docs/openlayers.md +98 -0
- package/docs/styles.md +103 -0
- package/package.json +51 -0
- package/packages/cesium/package.json +13 -0
- package/packages/cesium/src/index.js +405 -0
- package/packages/ol/package.json +14 -0
- package/packages/ol/src/index.js +1705 -0
- package/packages/ol/src/labels.js +977 -0
- package/src/3dtiles/b3dm.js +38 -0
- package/src/3dtiles/clipper-surfaces.js +317 -0
- package/src/3dtiles/export.js +768 -0
- package/src/3dtiles/extrude.js +301 -0
- package/src/3dtiles/flat.js +531 -0
- package/src/3dtiles/glb.js +178 -0
- package/src/3dtiles/gpkg-buildings.js +240 -0
- package/src/3dtiles/gpkg-features.js +157 -0
- package/src/3dtiles/tileset.js +75 -0
- package/src/build.js +134 -0
- package/src/cli.js +656 -0
- package/src/export-pmtiles.js +962 -0
- package/src/geometry-read.js +50 -0
- package/src/gpkg-read.js +460 -0
- package/src/gpkg.js +567 -0
- package/src/html.js +593 -0
- package/src/layers.js +357 -0
- package/src/manifest.js +29 -0
- package/src/mvt.js +2593 -0
- package/src/ol.js +5 -0
- package/src/osm.js +2110 -0
- package/src/pmtiles-worker.js +70 -0
- package/src/pmtiles.js +260 -0
- package/src/server.js +720 -0
- package/src/style-command.js +78 -0
- package/src/style-filters.js +76 -0
- package/src/style-presets.js +93 -0
- package/src/style-themes.js +235 -0
- package/src/style.js +13 -0
- package/src/tile-cache.js +59 -0
- package/src/utils.js +222 -0
- package/styles/presets/light.json +4655 -0
- package/styles/presets/monochrome.json +4655 -0
- package/styles/presets/neon-dark-3d.json +90 -0
- package/styles/presets/neon-dark.json +4690 -0
- package/styles/presets/tactical.json +4690 -0
- package/styles/themes/neon-dark.theme.json +20 -0
package/docs/styles.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Styles And Themes
|
|
2
|
+
|
|
3
|
+
Styles are external JSON documents. They are intentionally stored outside `data.gpkg` so the same package data can be rendered by OpenLayers, Cesium, or other clients.
|
|
4
|
+
|
|
5
|
+
There are three style-related concepts:
|
|
6
|
+
|
|
7
|
+
- **Full preset**: renderer-ready JSON under `styles/presets/`.
|
|
8
|
+
- **Compact theme**: small user-editable JSON under `styles/themes/`.
|
|
9
|
+
- **Package style**: generated or copied JSON inside `package.mapzero/styles/`, referenced by `manifest.json`.
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
Apply a full preset:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node src/cli.js style ./madrid.mapzero --preset neon-dark
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
List presets:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
node src/cli.js style ./madrid.mapzero --list-presets
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Apply a bundled theme:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
node src/cli.js style ./madrid.mapzero --theme neon-dark
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Apply a local theme file:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
node src/cli.js style ./madrid.mapzero --theme ./my.theme.json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Compact Theme Example
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"name": "my-neon",
|
|
42
|
+
"base": "neon-dark",
|
|
43
|
+
"colors": {
|
|
44
|
+
"background": "#03000a",
|
|
45
|
+
"roads": "#4fcfe3",
|
|
46
|
+
"roadsMajor": "#b8f7ff",
|
|
47
|
+
"buildings": "#ff00ff",
|
|
48
|
+
"water": "#0066ff",
|
|
49
|
+
"landuse": "#0f3d2e",
|
|
50
|
+
"labels": "#d9fbff",
|
|
51
|
+
"critical": "#ffcc33"
|
|
52
|
+
},
|
|
53
|
+
"intensity": {
|
|
54
|
+
"roads": 0.9,
|
|
55
|
+
"buildings": 0.8,
|
|
56
|
+
"labels": 0.85
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Most users should start with a compact theme. Full presets are useful when you need detailed control over layer rules.
|
|
62
|
+
|
|
63
|
+
## Full Style Rules
|
|
64
|
+
|
|
65
|
+
Layer rules can contain:
|
|
66
|
+
|
|
67
|
+
- `visibility`: `visible`, `minZoom`, `maxZoom`
|
|
68
|
+
- `body`: `color`, `width`, `opacity`, `widthScale`, `lineCap`, `lineJoin`
|
|
69
|
+
- `casing`: optional outer stroke
|
|
70
|
+
- `center`: optional center stroke
|
|
71
|
+
- `glow`: optional outer stroke
|
|
72
|
+
- `labels`: top-level label rules
|
|
73
|
+
- `bridge`, `tunnel`, `oneway`, `construction`, `restrictedAccess`: road semantic overlays
|
|
74
|
+
- `byProperty`: property-specific overrides such as `highway`, `admin_level`, or `aeroway`
|
|
75
|
+
- `categories` and `classes`: POI filtering rules
|
|
76
|
+
|
|
77
|
+
Aeronautical rules live under the `aip` layer key. The OSM property name remains `aeroway`.
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"visibility": { "visible": true, "minZoom": 8, "maxZoom": 22 },
|
|
84
|
+
"body": {
|
|
85
|
+
"color": "#4fcfe3",
|
|
86
|
+
"width": 1.1,
|
|
87
|
+
"opacity": 1,
|
|
88
|
+
"lineCap": "round",
|
|
89
|
+
"lineJoin": "round",
|
|
90
|
+
"widthScale": {
|
|
91
|
+
"stops": [[8, 0.18], [14, 1], [18, 2]]
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"casing": {
|
|
95
|
+
"enabled": true,
|
|
96
|
+
"color": "#063a46",
|
|
97
|
+
"width": 1.9,
|
|
98
|
+
"opacity": 1
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Style-only changes do not require rebuilding `data.gpkg`. Regenerate PMTiles or 3D Tiles only when tile content, filtering, or geometry generation changes.
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "map-zero",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Offline vector map packages from OSM, ready for GeoPackage, PMTiles, and 3D Tiles.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"map-zero": "./src/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "node src/cli.js --help",
|
|
12
|
+
"start": "node src/cli.js",
|
|
13
|
+
"check": "node --check src/cli.js && node --check src/build.js && node --check src/osm.js && node --check src/gpkg.js && node --check src/gpkg-read.js && node --check src/geometry-read.js && node --check src/html.js && node --check src/server.js && node --check src/mvt.js && node --check src/pmtiles.js && node --check src/pmtiles-worker.js && node --check src/export-pmtiles.js && node --check src/3dtiles/b3dm.js && node --check src/3dtiles/glb.js && node --check src/3dtiles/extrude.js && node --check src/3dtiles/flat.js && node --check src/3dtiles/clipper-surfaces.js && node --check src/3dtiles/gpkg-buildings.js && node --check src/3dtiles/gpkg-features.js && node --check src/3dtiles/tileset.js && node --check src/3dtiles/export.js && node --check test-3dtiles/generate-clipper-roads.mjs && node --check test-3dtiles/generate-clipper-roads-from-gpkg.mjs && node --check src/tile-cache.js && node --check src/style-filters.js && node --check src/style-command.js && node --check src/style-presets.js && node --check src/style-themes.js && node --check src/ol.js && node --check packages/ol/src/index.js && node --check packages/ol/src/labels.js && node --check packages/cesium/src/index.js && node --check src/layers.js && node --check src/style.js && node --check src/manifest.js && node --check src/utils.js"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"osm",
|
|
20
|
+
"openstreetmap",
|
|
21
|
+
"geopackage",
|
|
22
|
+
"mvt",
|
|
23
|
+
"pmtiles",
|
|
24
|
+
"3d-tiles",
|
|
25
|
+
"cesium",
|
|
26
|
+
"openlayers",
|
|
27
|
+
"offline-maps",
|
|
28
|
+
"vector-tiles"
|
|
29
|
+
],
|
|
30
|
+
"files": [
|
|
31
|
+
"src",
|
|
32
|
+
"packages",
|
|
33
|
+
"styles",
|
|
34
|
+
"docs",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"CHANGELOG.md"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"better-sqlite3": "^12.0.0",
|
|
41
|
+
"commander": "^14.0.0",
|
|
42
|
+
"earcut": "^3.0.2",
|
|
43
|
+
"fastify": "^5.8.5",
|
|
44
|
+
"geojson-vt": "^4.0.2",
|
|
45
|
+
"js-angusj-clipper": "^1.3.1",
|
|
46
|
+
"osm-pbf-parser": "^2.3.0",
|
|
47
|
+
"pmtiles": "^4.4.1",
|
|
48
|
+
"vt-pbf": "^3.1.3",
|
|
49
|
+
"wkx": "^0.5.0"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@map-zero/cesium",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Cesium integration helper for map-zero 3D Tiles packages.",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"cesium": "^1.141.0"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Cesium3DTileStyle,
|
|
3
|
+
Cesium3DTileset
|
|
4
|
+
} from 'cesium';
|
|
5
|
+
|
|
6
|
+
let autoInstanceCounter = 0;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {{
|
|
10
|
+
* id?: string,
|
|
11
|
+
* format?: string,
|
|
12
|
+
* version?: number,
|
|
13
|
+
* name?: string,
|
|
14
|
+
* bbox?: [number, number, number, number],
|
|
15
|
+
* styles?: Record<string, string>,
|
|
16
|
+
* tiles3d?: { format?: string, url?: string, layers?: string[] },
|
|
17
|
+
* cesium?: { tilesets?: Record<string, string>, bbox?: [number, number, number, number], focusBbox?: [number, number, number, number] },
|
|
18
|
+
* layers?: Array<{ id: string, table?: string, style?: string }>
|
|
19
|
+
* }} MapZeroManifest
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Load a map-zero manifest.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} manifestUrl
|
|
26
|
+
* @returns {Promise<MapZeroManifest>}
|
|
27
|
+
*/
|
|
28
|
+
export async function loadMapZeroManifest(manifestUrl) {
|
|
29
|
+
const response = await fetch(manifestUrl);
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`failed to load map-zero manifest: ${response.status}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return response.json();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Load a style document.
|
|
39
|
+
*
|
|
40
|
+
* Supported forms:
|
|
41
|
+
* - loadMapZeroStyle('./styles/neon-dark.json')
|
|
42
|
+
* - loadMapZeroStyle(manifest, { manifestUrl, style: 'default' })
|
|
43
|
+
*
|
|
44
|
+
* @param {string | MapZeroManifest} input
|
|
45
|
+
* @param {{ manifestUrl?: string, style?: string }} [options]
|
|
46
|
+
* @returns {Promise<Record<string, unknown> | null>}
|
|
47
|
+
*/
|
|
48
|
+
export async function loadMapZeroStyle(input, options = {}) {
|
|
49
|
+
if (typeof input === 'string') {
|
|
50
|
+
const response = await fetch(resolveRelativeUrl(input, globalThis.location?.href ?? 'http://localhost/'));
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
throw new Error(`failed to load map-zero style: ${response.status}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return response.json();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const manifest = input;
|
|
59
|
+
const key = options.style ?? 'default';
|
|
60
|
+
const stylePath = manifest.styles?.[key] ?? manifest.styles?.default;
|
|
61
|
+
if (!stylePath) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const response = await fetch(resolveRelativeUrl(stylePath, options.manifestUrl ?? globalThis.location?.href ?? 'http://localhost/'));
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
throw new Error(`failed to load map-zero style: ${response.status}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return response.json();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create Cesium 3D Tiles primitives for a map-zero package.
|
|
75
|
+
*
|
|
76
|
+
* @param {{
|
|
77
|
+
* id?: string,
|
|
78
|
+
* manifestUrl: string,
|
|
79
|
+
* manifest?: MapZeroManifest,
|
|
80
|
+
* style?: string | Record<string, unknown>,
|
|
81
|
+
* styleJson?: Record<string, unknown> | null,
|
|
82
|
+
* opacity?: number
|
|
83
|
+
* }} options
|
|
84
|
+
* @returns {Promise<{ id: string, manifest: MapZeroManifest, style: Record<string, unknown> | null, tilesets: Record<string, Cesium3DTileset> }>}
|
|
85
|
+
*/
|
|
86
|
+
export async function createMapZeroCesiumTilesets(options) {
|
|
87
|
+
const manifest = options.manifest ?? await loadMapZeroManifest(options.manifestUrl);
|
|
88
|
+
const instanceId = createInstanceId(options.id, manifest, options.manifestUrl);
|
|
89
|
+
const styleJson = options.styleJson ?? (
|
|
90
|
+
options.style && typeof options.style === 'object'
|
|
91
|
+
? options.style
|
|
92
|
+
: await loadMapZeroStyle(manifest, {
|
|
93
|
+
manifestUrl: options.manifestUrl,
|
|
94
|
+
style: typeof options.style === 'string' ? options.style : undefined
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const tilesetEntries = manifestTilesetEntries(manifest);
|
|
99
|
+
if (tilesetEntries.length === 0) {
|
|
100
|
+
return {
|
|
101
|
+
id: instanceId,
|
|
102
|
+
manifest,
|
|
103
|
+
style: styleJson,
|
|
104
|
+
tilesets: {}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @type {Record<string, Cesium3DTileset>} */
|
|
109
|
+
const tilesets = {};
|
|
110
|
+
for (const entry of tilesetEntries) {
|
|
111
|
+
const url = resolveRelativeUrl(entry.url, options.manifestUrl);
|
|
112
|
+
const tileset = await Cesium3DTileset.fromUrl(url);
|
|
113
|
+
tagCesiumTileset(tileset, instanceId, entry.layerId);
|
|
114
|
+
tileset.style = createMapZeroCesiumStyle(styleJson, {
|
|
115
|
+
layerId: entry.layerId,
|
|
116
|
+
visibleLayers: new Set([entry.layerId]),
|
|
117
|
+
opacity: options.opacity ?? 1
|
|
118
|
+
});
|
|
119
|
+
tilesets[entry.layerId] = tileset;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
id: instanceId,
|
|
124
|
+
manifest,
|
|
125
|
+
style: styleJson,
|
|
126
|
+
tilesets
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Add map-zero 3D Tiles to an existing Cesium Viewer.
|
|
132
|
+
*
|
|
133
|
+
* The helper does not create or own the viewer. It only adds map-zero
|
|
134
|
+
* primitives and returns a small controller.
|
|
135
|
+
*
|
|
136
|
+
* @param {{ scene: { primitives: { add: (primitive: unknown) => unknown, remove: (primitive: unknown) => boolean } } }} viewer
|
|
137
|
+
* @param {{
|
|
138
|
+
* id?: string,
|
|
139
|
+
* manifestUrl: string,
|
|
140
|
+
* style?: string | Record<string, unknown>,
|
|
141
|
+
* opacity?: number,
|
|
142
|
+
* zoomTo?: boolean,
|
|
143
|
+
* applyDefaultSceneStyle?: boolean,
|
|
144
|
+
* configureScene?: (viewer: unknown) => void
|
|
145
|
+
* }} options
|
|
146
|
+
* @returns {Promise<{
|
|
147
|
+
* id: string,
|
|
148
|
+
* manifest: MapZeroManifest,
|
|
149
|
+
* style: Record<string, unknown> | null,
|
|
150
|
+
* tilesets: Record<string, Cesium3DTileset>,
|
|
151
|
+
* setVisible: (layerId: string, visible: boolean) => void,
|
|
152
|
+
* setOpacity: (layerId: string, opacity: number) => void,
|
|
153
|
+
* destroy: () => void
|
|
154
|
+
* }>}
|
|
155
|
+
*/
|
|
156
|
+
export async function addMapZeroToCesium(viewer, options) {
|
|
157
|
+
if (options.applyDefaultSceneStyle) {
|
|
158
|
+
applyMapZeroCesiumSceneStyle(viewer);
|
|
159
|
+
}
|
|
160
|
+
if (typeof options.configureScene === 'function') {
|
|
161
|
+
options.configureScene(viewer);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const result = await createMapZeroCesiumTilesets(options);
|
|
165
|
+
const uniqueTilesets = [...new Set(Object.values(result.tilesets))];
|
|
166
|
+
const visibleLayers = new Set(Object.keys(result.tilesets));
|
|
167
|
+
let opacity = options.opacity ?? 1;
|
|
168
|
+
|
|
169
|
+
for (const tileset of uniqueTilesets) {
|
|
170
|
+
viewer.scene.primitives.add(tileset);
|
|
171
|
+
}
|
|
172
|
+
if (options.zoomTo !== false && typeof viewer.zoomTo === 'function') {
|
|
173
|
+
const firstTileset = uniqueTilesets[0];
|
|
174
|
+
if (firstTileset) {
|
|
175
|
+
await viewer.zoomTo(firstTileset);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
manifest: result.manifest,
|
|
181
|
+
id: result.id,
|
|
182
|
+
style: result.style,
|
|
183
|
+
tilesets: result.tilesets,
|
|
184
|
+
setVisible(layerId, visible) {
|
|
185
|
+
const tileset = result.tilesets[layerId];
|
|
186
|
+
if (tileset) {
|
|
187
|
+
if (visible) {
|
|
188
|
+
visibleLayers.add(layerId);
|
|
189
|
+
} else {
|
|
190
|
+
visibleLayers.delete(layerId);
|
|
191
|
+
}
|
|
192
|
+
applyStyleToTilesetMap(result.tilesets, result.style, {
|
|
193
|
+
opacity,
|
|
194
|
+
visibleLayers
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
setOpacity(layerId, nextOpacity) {
|
|
199
|
+
if (!result.tilesets[layerId]) return;
|
|
200
|
+
opacity = clamp01(Number(nextOpacity));
|
|
201
|
+
applyStyleToTilesetMap(result.tilesets, result.style, {
|
|
202
|
+
opacity,
|
|
203
|
+
visibleLayers
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
destroy() {
|
|
207
|
+
for (const tileset of uniqueTilesets) {
|
|
208
|
+
viewer.scene.primitives.remove(tileset);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Apply optional tactical scene defaults for the built-in map-zero viewer.
|
|
216
|
+
*
|
|
217
|
+
* This is intentionally opt-in. External applications should keep full control
|
|
218
|
+
* of their Cesium Viewer and call this helper only when they want the map-zero
|
|
219
|
+
* black-background tactical look.
|
|
220
|
+
*
|
|
221
|
+
* @param {any} viewer
|
|
222
|
+
*/
|
|
223
|
+
export function applyMapZeroCesiumSceneStyle(viewer) {
|
|
224
|
+
const Cesium = globalThis.Cesium;
|
|
225
|
+
const scene = viewer?.scene;
|
|
226
|
+
if (!scene || !Cesium) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
scene.backgroundColor = Cesium.Color.BLACK;
|
|
231
|
+
if (scene.globe) {
|
|
232
|
+
scene.globe.baseColor = Cesium.Color.BLACK;
|
|
233
|
+
scene.globe.enableLighting = false;
|
|
234
|
+
scene.globe.depthTestAgainstTerrain = false;
|
|
235
|
+
}
|
|
236
|
+
if (scene.fog) scene.fog.enabled = false;
|
|
237
|
+
if (scene.skyBox) scene.skyBox.show = false;
|
|
238
|
+
if (scene.sun) scene.sun.show = false;
|
|
239
|
+
if (scene.moon) scene.moon.show = false;
|
|
240
|
+
if (scene.skyAtmosphere) scene.skyAtmosphere.show = false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Convert a map-zero layer style to a Cesium 3D Tiles style.
|
|
245
|
+
*
|
|
246
|
+
* @param {Record<string, unknown> | null} styleJson
|
|
247
|
+
* @param {{ layerId: string, opacity?: number, visibleLayers?: Set<string> }} options
|
|
248
|
+
* @returns {Cesium3DTileStyle}
|
|
249
|
+
*/
|
|
250
|
+
export function createMapZeroCesiumStyle(styleJson, options) {
|
|
251
|
+
const visibleLayers = options.visibleLayers ?? new Set([options.layerId]);
|
|
252
|
+
if (!visibleLayers.has(options.layerId)) {
|
|
253
|
+
return new Cesium3DTileStyle({
|
|
254
|
+
show: false
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const rule = layerStyle(styleJson, options.layerId);
|
|
259
|
+
const { color, opacity } = cesiumLayerMaterial(rule, options.layerId);
|
|
260
|
+
return new Cesium3DTileStyle({
|
|
261
|
+
color: `color('${safeCssColor(color)}', ${clamp01(Number(options.opacity ?? 1) * opacity).toFixed(3)})`,
|
|
262
|
+
show: true
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Pick a single material color from a map-zero style rule.
|
|
268
|
+
*
|
|
269
|
+
* In 2D, buildings commonly use a dark fill plus a bright stroke. A single
|
|
270
|
+
* Cesium material cannot show that outline, so building solids use the body or
|
|
271
|
+
* stroke color instead of the dark fill.
|
|
272
|
+
*
|
|
273
|
+
* @param {Record<string, any> | null} rule
|
|
274
|
+
* @param {string} layerId
|
|
275
|
+
* @returns {{ color: string, opacity: number }}
|
|
276
|
+
*/
|
|
277
|
+
function cesiumLayerMaterial(rule, layerId) {
|
|
278
|
+
if (layerId === 'buildings') {
|
|
279
|
+
return {
|
|
280
|
+
color: String(rule?.body?.color ?? rule?.stroke ?? rule?.fill ?? '#ff00ff'),
|
|
281
|
+
opacity: clamp01(Number(rule?.body?.opacity ?? rule?.strokeOpacity ?? rule?.fillOpacity ?? 0.8))
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
color: String(rule?.fill ?? rule?.body?.color ?? rule?.stroke ?? '#00ffff'),
|
|
287
|
+
opacity: clamp01(Number(rule?.fillOpacity ?? rule?.body?.opacity ?? rule?.strokeOpacity ?? 0.8))
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* @param {Record<string, Cesium3DTileset>} tilesets
|
|
293
|
+
* @param {Record<string, unknown> | null} style
|
|
294
|
+
* @param {{ opacity: number, visibleLayers: Set<string> }} options
|
|
295
|
+
*/
|
|
296
|
+
function applyStyleToTilesetMap(tilesets, style, options) {
|
|
297
|
+
for (const [layerId, tileset] of Object.entries(tilesets)) {
|
|
298
|
+
tileset.style = createMapZeroCesiumStyle(style, {
|
|
299
|
+
layerId,
|
|
300
|
+
opacity: options.opacity,
|
|
301
|
+
visibleLayers: options.visibleLayers
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* @param {Record<string, unknown> | null} styleJson
|
|
308
|
+
* @param {string} layerId
|
|
309
|
+
* @returns {Record<string, any> | null}
|
|
310
|
+
*/
|
|
311
|
+
function layerStyle(styleJson, layerId) {
|
|
312
|
+
const layers = /** @type {{ layers?: Record<string, unknown> } | null} */ (styleJson)?.layers;
|
|
313
|
+
return /** @type {Record<string, any> | null} */ (layers?.[layerId] ?? null);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* @param {MapZeroManifest} manifest
|
|
318
|
+
* @returns {Array<{ layerId: string, url: string }>}
|
|
319
|
+
*/
|
|
320
|
+
function manifestTilesetEntries(manifest) {
|
|
321
|
+
const cesiumTilesets = manifest.cesium?.tilesets;
|
|
322
|
+
if (cesiumTilesets && typeof cesiumTilesets === 'object') {
|
|
323
|
+
return Object.entries(cesiumTilesets)
|
|
324
|
+
.filter(([, url]) => typeof url === 'string' && url.length > 0)
|
|
325
|
+
.map(([layerId, url]) => ({ layerId, url }));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (manifest.tiles3d?.format === '3dtiles' && typeof manifest.tiles3d.url === 'string') {
|
|
329
|
+
const layers = Array.isArray(manifest.tiles3d.layers) && manifest.tiles3d.layers.length > 0
|
|
330
|
+
? manifest.tiles3d.layers.map(String)
|
|
331
|
+
: ['buildings'];
|
|
332
|
+
return layers.map((layerId) => ({
|
|
333
|
+
layerId,
|
|
334
|
+
url: /** @type {string} */ (manifest.tiles3d?.url)
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return [];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* @param {string | undefined} id
|
|
343
|
+
* @param {MapZeroManifest} manifest
|
|
344
|
+
* @param {string} manifestUrl
|
|
345
|
+
* @returns {string}
|
|
346
|
+
*/
|
|
347
|
+
function createInstanceId(id, manifest, manifestUrl) {
|
|
348
|
+
if (id) {
|
|
349
|
+
return safeInstanceId(id);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const name = typeof manifest.name === 'string' && manifest.name.trim()
|
|
353
|
+
? manifest.name
|
|
354
|
+
: new URL(manifestUrl, globalThis.location?.href ?? 'http://localhost/').pathname.split('/').filter(Boolean).at(-2) ?? 'mapzero';
|
|
355
|
+
autoInstanceCounter += 1;
|
|
356
|
+
return `${safeInstanceId(name)}-${autoInstanceCounter}`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* @param {string} id
|
|
361
|
+
* @returns {string}
|
|
362
|
+
*/
|
|
363
|
+
function safeInstanceId(id) {
|
|
364
|
+
return id.toLowerCase().replace(/[^a-z0-9_-]+/g, '-').replace(/^-+|-+$/g, '') || 'mapzero';
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* @param {Cesium3DTileset} tileset
|
|
369
|
+
* @param {string} instanceId
|
|
370
|
+
* @param {string} layerId
|
|
371
|
+
*/
|
|
372
|
+
function tagCesiumTileset(tileset, instanceId, layerId) {
|
|
373
|
+
tileset.mapZero = {
|
|
374
|
+
id: instanceId,
|
|
375
|
+
layerId,
|
|
376
|
+
namespacedLayerId: `${instanceId}:${layerId}`
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* @param {string} path
|
|
382
|
+
* @param {string} baseUrl
|
|
383
|
+
* @returns {string}
|
|
384
|
+
*/
|
|
385
|
+
function resolveRelativeUrl(path, baseUrl) {
|
|
386
|
+
const absoluteBase = new URL(baseUrl, globalThis.location?.href ?? 'http://localhost/').href;
|
|
387
|
+
return new URL(path, absoluteBase).toString();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* @param {string} color
|
|
392
|
+
* @returns {string}
|
|
393
|
+
*/
|
|
394
|
+
function safeCssColor(color) {
|
|
395
|
+
return /^#[0-9a-f]{6}$/i.test(color) ? color : '#ff00ff';
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* @param {number} value
|
|
401
|
+
* @returns {number}
|
|
402
|
+
*/
|
|
403
|
+
function clamp01(value) {
|
|
404
|
+
return Math.max(0, Math.min(1, Number.isFinite(value) ? value : 1));
|
|
405
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@map-zero/ol",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "OpenLayers integration helper for map-zero packages.",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"ol": "^10.9.0",
|
|
12
|
+
"pmtiles": "^4.4.1"
|
|
13
|
+
}
|
|
14
|
+
}
|