mirror of
https://github.com/maputnik/editor.git
synced 2026-06-19 05:37:27 +00:00
Add PMTiles vector support
* add PMTiles mode to source editing dialog * App.tsx detects PMTiles sources for fetching vector_layers (inspect does not yet work)
This commit is contained in:
committed by
Pirmin Kalberer
parent
d50ea76347
commit
f9db0d9b5c
Generated
+26
@@ -43,6 +43,7 @@
|
|||||||
"maputnik-design": "github:maputnik/design#172b06c",
|
"maputnik-design": "github:maputnik/design#172b06c",
|
||||||
"ol": "^6.14.1",
|
"ol": "^6.14.1",
|
||||||
"ol-mapbox-style": "^7.1.1",
|
"ol-mapbox-style": "^7.1.1",
|
||||||
|
"pmtiles": "^3.1.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-accessible-accordion": "^5.0.0",
|
"react-accessible-accordion": "^5.0.0",
|
||||||
@@ -2132,6 +2133,15 @@
|
|||||||
"integrity": "sha512-131wOmuwDg8ypYCSQ437bGdP+K2lJ8GJUu+ng4iQQxAc3irRnb7mGHbexsPChBcKWLctTR9V5LJdX5A8WWk44A==",
|
"integrity": "sha512-131wOmuwDg8ypYCSQ437bGdP+K2lJ8GJUu+ng4iQQxAc3irRnb7mGHbexsPChBcKWLctTR9V5LJdX5A8WWk44A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/leaflet": {
|
||||||
|
"version": "1.9.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.12.tgz",
|
||||||
|
"integrity": "sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/geojson": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/lodash": {
|
"node_modules/@types/lodash": {
|
||||||
"version": "4.17.0",
|
"version": "4.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
|
||||||
@@ -5177,6 +5187,12 @@
|
|||||||
"pend": "~1.2.0"
|
"pend": "~1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fflate": {
|
||||||
|
"version": "0.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||||
|
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/figures": {
|
"node_modules/figures": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
||||||
@@ -8844,6 +8860,16 @@
|
|||||||
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.3.tgz",
|
||||||
"integrity": "sha512-VJK1SRmXBpjwsB4YOHYSturx48rLKMzHgCqDH2ZDa6ZbMS/N5huoNqyQdK5Fj/xayu3fqbXckn5SeCS1EbMDZg=="
|
"integrity": "sha512-VJK1SRmXBpjwsB4YOHYSturx48rLKMzHgCqDH2ZDa6ZbMS/N5huoNqyQdK5Fj/xayu3fqbXckn5SeCS1EbMDZg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/pmtiles": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pmtiles/-/pmtiles-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-6JvgAQ8gElP1Ilg6ILM4KqleeKS+QcwpW8PXqhPWjRFmqF42yyUJ8sP3dZHQXm+G0HYXuw1kGlMTdVEs583pCQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/leaflet": "^1.9.8",
|
||||||
|
"fflate": "^0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/possible-typed-array-names": {
|
"node_modules/possible-typed-array-names": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
"maputnik-design": "github:maputnik/design#172b06c",
|
"maputnik-design": "github:maputnik/design#172b06c",
|
||||||
"ol": "^6.14.1",
|
"ol": "^6.14.1",
|
||||||
"ol-mapbox-style": "^7.1.1",
|
"ol-mapbox-style": "^7.1.1",
|
||||||
|
"pmtiles": "^3.1.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-accessible-accordion": "^5.0.0",
|
"react-accessible-accordion": "^5.0.0",
|
||||||
|
|||||||
+35
-25
@@ -8,6 +8,7 @@ import get from 'lodash.get'
|
|||||||
import {unset} from 'lodash'
|
import {unset} from 'lodash'
|
||||||
import {arrayMoveMutable} from 'array-move'
|
import {arrayMoveMutable} from 'array-move'
|
||||||
import hash from "string-hash";
|
import hash from "string-hash";
|
||||||
|
import { PMTiles } from "pmtiles";
|
||||||
import {Map, LayerSpecification, StyleSpecification, ValidationError, SourceSpecification} from 'maplibre-gl'
|
import {Map, LayerSpecification, StyleSpecification, ValidationError, SourceSpecification} from 'maplibre-gl'
|
||||||
import {latest, validateStyleMin} from '@maplibre/maplibre-gl-style-spec'
|
import {latest, validateStyleMin} from '@maplibre/maplibre-gl-style-spec'
|
||||||
|
|
||||||
@@ -641,33 +642,42 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
console.warn("Failed to setFetchAccessToken: ", err);
|
console.warn("Failed to setFetchAccessToken: ", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(url!, {
|
const setVectorLayers = (json:any) => {
|
||||||
mode: 'cors',
|
if(!Object.prototype.hasOwnProperty.call(json, "vector_layers")) {
|
||||||
})
|
return;
|
||||||
.then(response => response.json())
|
}
|
||||||
.then(json => {
|
|
||||||
|
|
||||||
if(!Object.prototype.hasOwnProperty.call(json, "vector_layers")) {
|
// Create new objects before setState
|
||||||
return;
|
const sources = Object.assign({}, {
|
||||||
}
|
[key]: this.state.sources[key],
|
||||||
|
|
||||||
// Create new objects before setState
|
|
||||||
const sources = Object.assign({}, {
|
|
||||||
[key]: this.state.sources[key],
|
|
||||||
});
|
|
||||||
|
|
||||||
for(const layer of json.vector_layers) {
|
|
||||||
(sources[key] as any).layers.push(layer.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.debug("Updating source: "+key);
|
|
||||||
this.setState({
|
|
||||||
sources: sources
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error("Failed to process sources for '%s'", url, err);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for(const layer of json.vector_layers) {
|
||||||
|
(sources[key] as any).layers.push(layer.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug("Updating source: "+key);
|
||||||
|
this.setState({
|
||||||
|
sources: sources
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (url!.startsWith("pmtiles://")) {
|
||||||
|
(new PMTiles(url!.substr(10))).getTileJson("")
|
||||||
|
.then(json => setVectorLayers(json))
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Failed to process sources for '%s'", url, err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fetch(url!, {
|
||||||
|
mode: 'cors',
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(json => setVectorLayers(json))
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Failed to process sources for '%s'", url, err);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sourceList[key] = this.state.sources[key] || this.state.mapStyle.sources[key];
|
sourceList[key] = this.state.sources[key] || this.state.mapStyle.sources[key];
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import MaplibreGeocoder, { MaplibreGeocoderApi, MaplibreGeocoderApiConfig } from
|
|||||||
import '@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css';
|
import '@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css';
|
||||||
import { withTranslation, WithTranslation } from 'react-i18next'
|
import { withTranslation, WithTranslation } from 'react-i18next'
|
||||||
import i18next from 'i18next'
|
import i18next from 'i18next'
|
||||||
|
import { Protocol } from "pmtiles";
|
||||||
|
|
||||||
function renderPopup(popup: JSX.Element, mountNode: ReactDOM.Container): HTMLElement {
|
function renderPopup(popup: JSX.Element, mountNode: ReactDOM.Container): HTMLElement {
|
||||||
ReactDOM.render(popup, mountNode);
|
ReactDOM.render(popup, mountNode);
|
||||||
@@ -148,6 +149,8 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
localIdeographFontFamily: false
|
localIdeographFontFamily: false
|
||||||
} satisfies MapOptions;
|
} satisfies MapOptions;
|
||||||
|
|
||||||
|
let protocol = new Protocol();
|
||||||
|
MapLibreGl.addProtocol("pmtiles",protocol.tile);
|
||||||
const map = new MapLibreGl.Map(mapOpts);
|
const map = new MapLibreGl.Map(mapOpts);
|
||||||
|
|
||||||
const mapViewChange = () => {
|
const mapViewChange = () => {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ function editorMode(source: SourceSpecification) {
|
|||||||
}
|
}
|
||||||
if(source.type === 'vector') {
|
if(source.type === 'vector') {
|
||||||
if(source.tiles) return 'tile_vector'
|
if(source.tiles) return 'tile_vector'
|
||||||
|
if(source.url!.startsWith("pmtiles://")) return 'pmtiles_vector'
|
||||||
return 'tilejson_vector'
|
return 'tilejson_vector'
|
||||||
}
|
}
|
||||||
if(source.type === 'geojson') {
|
if(source.type === 'geojson') {
|
||||||
@@ -129,6 +130,10 @@ class AddSource extends React.Component<AddSourceProps, AddSourceState> {
|
|||||||
const {protocol} = window.location;
|
const {protocol} = window.location;
|
||||||
|
|
||||||
switch(mode) {
|
switch(mode) {
|
||||||
|
case 'pmtiles_vector': return {
|
||||||
|
type: 'vector',
|
||||||
|
url: `${protocol}//localhost:3000/file.pmtiles`
|
||||||
|
}
|
||||||
case 'geojson_url': return {
|
case 'geojson_url': return {
|
||||||
type: 'geojson',
|
type: 'geojson',
|
||||||
data: `${protocol}//localhost:3000/geojson.json`
|
data: `${protocol}//localhost:3000/geojson.json`
|
||||||
@@ -240,6 +245,7 @@ class AddSource extends React.Component<AddSourceProps, AddSourceState> {
|
|||||||
['tile_raster', t('Raster (Tile URLs)')],
|
['tile_raster', t('Raster (Tile URLs)')],
|
||||||
['tilejson_raster-dem', t('Raster DEM (TileJSON URL)')],
|
['tilejson_raster-dem', t('Raster DEM (TileJSON URL)')],
|
||||||
['tilexyz_raster-dem', t('Raster DEM (XYZ URLs)')],
|
['tilexyz_raster-dem', t('Raster DEM (XYZ URLs)')],
|
||||||
|
['pmtiles_vector', 'Vector (PMTiles)'],
|
||||||
['image', t('Image')],
|
['image', t('Image')],
|
||||||
['video', t('Video')],
|
['video', t('Video')],
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import FieldCheckbox from './FieldCheckbox'
|
|||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { WithTranslation, withTranslation } from 'react-i18next';
|
||||||
import { TFunction } from 'i18next'
|
import { TFunction } from 'i18next'
|
||||||
|
|
||||||
export type EditorMode = "video" | "image" | "tilejson_vector" | "tile_raster" | "tilejson_raster" | "tilexyz_raster-dem" | "tilejson_raster-dem" | "tile_vector" | "geojson_url" | "geojson_json" | null;
|
export type EditorMode = "video" | "image" | "tilejson_vector" | "tile_raster" | "tilejson_raster" | "tilexyz_raster-dem" | "tilejson_raster-dem" | "pmtiles_vector" | "tile_vector" | "geojson_url" | "geojson_json" | null;
|
||||||
|
|
||||||
type TileJSONSourceEditorProps = {
|
type TileJSONSourceEditorProps = {
|
||||||
source: {
|
source: {
|
||||||
@@ -286,6 +286,32 @@ class GeoJSONSourceFieldJsonEditor extends React.Component<GeoJSONSourceFieldJso
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PMTilesSourceEditorProps = {
|
||||||
|
source: {
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
onChange(...args: unknown[]): unknown
|
||||||
|
children?: React.ReactNode
|
||||||
|
} & WithTranslation;
|
||||||
|
|
||||||
|
class PMTilesSourceEditor extends React.Component<PMTilesSourceEditorProps> {
|
||||||
|
render() {
|
||||||
|
const t = this.props.t;
|
||||||
|
return <div>
|
||||||
|
<FieldUrl
|
||||||
|
label={t("PMTiles URL")}
|
||||||
|
fieldSpec={latest.source_vector.url}
|
||||||
|
value={this.props.source.url}
|
||||||
|
onChange={url => this.props.onChange({
|
||||||
|
...this.props.source,
|
||||||
|
url: `pmtiles://${url}`
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type ModalSourcesTypeEditorInternalProps = {
|
type ModalSourcesTypeEditorInternalProps = {
|
||||||
mode: EditorMode
|
mode: EditorMode
|
||||||
source: any
|
source: any
|
||||||
@@ -343,6 +369,7 @@ class ModalSourcesTypeEditorInternal extends React.Component<ModalSourcesTypeEdi
|
|||||||
value={this.props.source.encoding || latest.source_raster_dem.encoding.default}
|
value={this.props.source.encoding || latest.source_raster_dem.encoding.default}
|
||||||
/>
|
/>
|
||||||
</TileURLSourceEditor>
|
</TileURLSourceEditor>
|
||||||
|
case 'pmtiles_vector': return <PMTilesSourceEditor {...commonProps} />
|
||||||
case 'image': return <ImageSourceEditor {...commonProps} />
|
case 'image': return <ImageSourceEditor {...commonProps} />
|
||||||
case 'video': return <VideoSourceEditor {...commonProps} />
|
case 'video': return <VideoSourceEditor {...commonProps} />
|
||||||
default: return null
|
default: return null
|
||||||
|
|||||||
Reference in New Issue
Block a user