Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PMTiles support #938

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
18 changes: 18 additions & 0 deletions cypress/e2e/modals.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,24 @@ describe("modals", () => {
});
});

it("add new pmtiles source", () => {
let sourceId = "pmtilestest";
when.setValue("modal:sources.add.source_id", sourceId);
when.select("modal:sources.add.source_type", "pmtiles_vector");
when.setValue("modal:sources.add.source_url", "https://data.source.coop/protomaps/openstreetmap/v4.pmtiles");
when.click("modal:sources.add.add_source");
when.click("modal:sources.add.add_source");
when.wait(200);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
sources: {
pmtilestest: {
type: "vector",
url: "pmtiles://https://data.source.coop/protomaps/openstreetmap/v4.pmtiles",
},
},
});
});

it("add new raster source", () => {
let sourceId = "rastertest";
when.setValue("modal:sources.add.source_id", sourceId);
Expand Down
16 changes: 16 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"maputnik-design": "github:maputnik/design#172b06c",
"ol": "^6.14.1",
"ol-mapbox-style": "^7.1.1",
"pmtiles": "^4.1.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-accessible-accordion": "^5.0.0",
Expand Down
51 changes: 30 additions & 21 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import get from 'lodash.get'
import {unset} from 'lodash'
import {arrayMoveMutable} from 'array-move'
import hash from "string-hash";
import { PMTiles } from "pmtiles";
import {Map, LayerSpecification, StyleSpecification, ValidationError, SourceSpecification} from 'maplibre-gl'
import {latest, validateStyleMin} from '@maplibre/maplibre-gl-style-spec'

Expand Down Expand Up @@ -641,33 +642,41 @@ export default class App extends React.Component<any, AppState> {
console.warn("Failed to setFetchAccessToken: ", err);
}

fetch(url!, {
mode: 'cors',
})
.then(response => response.json())
.then(json => {
const setVectorLayers = (json:any) => {
if(!Object.prototype.hasOwnProperty.call(json, "vector_layers")) {
return;
}

if(!Object.prototype.hasOwnProperty.call(json, "vector_layers")) {
return;
}
// Create new objects before setState
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)
}

for(const layer of json.vector_layers) {
(sources[key] as any).layers.push(layer.id)
}
this.setState({
sources: sources
});
};

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',
})
.catch(err => {
console.error("Failed to process sources for '%s'", url, err);
});
.then(response => response.json())
.then(json => setVectorLayers(json))
.catch(err => {
console.error("Failed to process sources for '%s'", url, err);
});
}
}
else {
sourceList[key] = this.state.sources[key] || this.state.mapStyle.sources[key];
Expand Down
3 changes: 3 additions & 0 deletions src/components/MapMaplibreGl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import MaplibreGeocoder, { MaplibreGeocoderApi, MaplibreGeocoderApiConfig } from
import '@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css';
import { withTranslation, WithTranslation } from 'react-i18next'
import i18next from 'i18next'
import { Protocol } from "pmtiles";

function renderPopup(popup: JSX.Element, mountNode: ReactDOM.Container): HTMLElement {
ReactDOM.render(popup, mountNode);
Expand Down Expand Up @@ -148,6 +149,8 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
localIdeographFontFamily: false
} satisfies MapOptions;

let protocol = new Protocol({metadata: true});
MapLibreGl.addProtocol("pmtiles",protocol.tile);
const map = new MapLibreGl.Map(mapOpts);

const mapViewChange = () => {
Expand Down
6 changes: 6 additions & 0 deletions src/components/ModalSources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function editorMode(source: SourceSpecification) {
}
if(source.type === 'vector') {
if(source.tiles) return 'tile_vector'
if(source.url && source.url.startsWith("pmtiles://")) return 'pmtiles_vector'
return 'tilejson_vector'
}
if(source.type === 'geojson') {
Expand Down Expand Up @@ -129,6 +130,10 @@ class AddSource extends React.Component<AddSourceProps, AddSourceState> {
const {protocol} = window.location;

switch(mode) {
case 'pmtiles_vector': return {
type: 'vector',
url: `${protocol}//localhost:3000/file.pmtiles`
}
case 'geojson_url': return {
type: 'geojson',
data: `${protocol}//localhost:3000/geojson.json`
Expand Down Expand Up @@ -240,6 +245,7 @@ class AddSource extends React.Component<AddSourceProps, AddSourceState> {
['tile_raster', t('Raster (Tile URLs)')],
['tilejson_raster-dem', t('Raster DEM (TileJSON URL)')],
['tilexyz_raster-dem', t('Raster DEM (XYZ URLs)')],
['pmtiles_vector', t('Vector (PMTiles)')],
['image', t('Image')],
['video', t('Video')],
]}
Expand Down
30 changes: 29 additions & 1 deletion src/components/ModalSourcesTypeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import FieldCheckbox from './FieldCheckbox'
import { WithTranslation, withTranslation } from 'react-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 = {
source: {
Expand Down Expand Up @@ -286,6 +286,33 @@ 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}
data-wd-key="modal:sources.add.source_url"
onChange={(url: string) => this.props.onChange({
...this.props.source,
url: url.startsWith("pmtiles://") ? url : `pmtiles://${url}`
})}
/>
{this.props.children}
</div>
}
}

type ModalSourcesTypeEditorInternalProps = {
mode: EditorMode
source: any
Expand Down Expand Up @@ -343,6 +370,7 @@ class ModalSourcesTypeEditorInternal extends React.Component<ModalSourcesTypeEdi
value={this.props.source.encoding || latest.source_raster_dem.encoding.default}
/>
</TileURLSourceEditor>
case 'pmtiles_vector': return <PMTilesSourceEditor {...commonProps} />
case 'image': return <ImageSourceEditor {...commonProps} />
case 'video': return <VideoSourceEditor {...commonProps} />
default: return null
Expand Down
2 changes: 2 additions & 0 deletions src/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
"Raster (Tile URLs)": "Raster (Tile URLs)",
"Raster DEM (TileJSON URL)": "Raster DEM (TileJSON URL)",
"Raster DEM (XYZ URLs)": "Raster DEM (XYZ URLs)",
"Vector (PMTiles)": "Vektor (PMTiles)",
"Image": "Bild",
"Video": "Video",
"Add Source": "Quelle hinzufügen",
Expand All @@ -170,6 +171,7 @@
"GeoJSON URL": "GeoJSON URL",
"GeoJSON": "GeoJSON",
"Cluster": "Cluster",
"PMTiles URL": "PMTiles URL",
"Tile Size": "Kachelgröße",
"Encoding": "Kodierung",
"Error:": "Fehler:",
Expand Down
2 changes: 2 additions & 0 deletions src/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
"Raster (Tile URLs)": "Raster (URLs Tile)",
"Raster DEM (TileJSON URL)": "Raster DEM (URL TileJSON)",
"Raster DEM (XYZ URLs)": "Raster DEM (URLs XYZ)",
"Vector (PMTiles)": "Vecteur (PMTiles)",
"Image": "Image",
"Video": "Vidéo",
"Add Source": "Ajouter une source",
Expand All @@ -170,6 +171,7 @@
"GeoJSON URL": "URL GeoJSON",
"GeoJSON": "GeoJSON",
"Cluster": "Cluster",
"PMTiles URL": "URL PMTiles",
"Tile Size": "Dimension d'une tuile",
"Encoding": "Encodage",
"Error:": "Erreur :",
Expand Down
2 changes: 2 additions & 0 deletions src/locales/he/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
"Raster (Tile URLs)": "Raster (Tile URLs)",
"Raster DEM (TileJSON URL)": "Raster DEM (TileJSON URL)",
"Raster DEM (XYZ URLs)": "Raster DEM (XYZ URLs)",
"Vector (PMTiles)": "Vector (PMTiles)",
"Image": "תמונה",
"Video": "וידאו",
"Add Source": "הוספת מקור",
Expand All @@ -170,6 +171,7 @@
"GeoJSON URL": "כתובת GeoJSON",
"GeoJSON": "GeoJSON",
"Cluster": "קיבוץ",
"PMTiles URL": "כתובת PMTiles",
"Tile Size": "גודל אריח",
"Encoding": "קידוד",
"Error:": "שגיאה",
Expand Down
2 changes: 2 additions & 0 deletions src/locales/ja/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
"Raster (Tile URLs)": "ラスタ (Tile URLs)",
"Raster DEM (TileJSON URL)": "ラスタ DEM (TileJSON URL)",
"Raster DEM (XYZ URLs)": "ラスタ DEM (XYZ URL)",
"Vector (PMTiles)": "__STRING_NOT_TRANSLATED__",
"Image": "画像",
"Video": "動画",
"Add Source": "ソースを追加",
Expand All @@ -170,6 +171,7 @@
"GeoJSON URL": "GeoJSON URL",
"GeoJSON": "GeoJSON",
"Cluster": "クラスタ",
"PMTiles URL": "__STRING_NOT_TRANSLATED__",
"Tile Size": "タイルサイズ",
"Encoding": "エンコーディング",
"Error:": "エラー:",
Expand Down
2 changes: 2 additions & 0 deletions src/locales/zh/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
"Raster (Tile URLs)": "栅格数据 (Tile URLs)",
"Raster DEM (TileJSON URL)": "栅格高程数据 (TileJSON URL)",
"Raster DEM (XYZ URLs)": "栅格高程数据 (XYZ URLs)",
"Vector (PMTiles)": "__STRING_NOT_TRANSLATED__",
"Image": "图像",
"Video": "视频",
"Add Source": "添加源",
Expand All @@ -170,6 +171,7 @@
"GeoJSON URL": "GeoJSON URL",
"GeoJSON": "GeoJSON",
"Cluster": "聚合",
"PMTiles URL": "__STRING_NOT_TRANSLATED__",
"Tile Size": "__STRING_NOT_TRANSLATED__",
"Encoding": "编码",
"Error:": "错误:",
Expand Down
Loading