Add globe support in Maputnik UI (#1379)

## Launch Checklist

Add a small drop down to select mercator or globe.
This isn't a fully covered field as one can set an expression there, but
I believe this is good enough for most cases.

Before:
<img width="645" height="254" alt="image"
src="https://github.com/user-attachments/assets/19a7ec50-a0bb-4ea3-b9fc-3abc5572c47e"
/>
After:
<img width="770" height="462" alt="image"
src="https://github.com/user-attachments/assets/f4774020-1cc8-45fe-88f9-f77ad7c53140"
/>


 - [x] Briefly describe the changes in this PR.
- [x] Include before/after visuals or gifs if this PR includes visual
changes.
 - [x] Write tests for all new functionality.
 - [x] Add an entry to `CHANGELOG.md` under the `## main` section.

---------

Co-authored-by: Birk Skyum <birk.skyum@pm.me>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Harel M
2025-09-14 12:48:29 +03:00
committed by GitHub
parent 56cdfd23df
commit 5312d61598
3 changed files with 78 additions and 22 deletions

View File

@@ -1,6 +1,6 @@
import React from "react";
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
import type {LightSpecification, StyleSpecification, TerrainSpecification, TransitionSpecification} from "maplibre-gl";
import type {LightSpecification, ProjectionSpecification, StyleSpecification, TerrainSpecification, TransitionSpecification} from "maplibre-gl";
import { type WithTranslation, withTranslation } from "react-i18next";
import FieldArray from "../FieldArray";
@@ -79,6 +79,24 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
});
}
changeProjectionType(value: any) {
const projection = {
...this.props.mapStyle.projection,
} as ProjectionSpecification;
if (value === undefined) {
delete projection.type;
}
else {
projection.type = value;
}
this.props.onStyleChanged({
...this.props.mapStyle,
projection,
});
}
changeStyleProperty(property: keyof StyleSpecification | "owner", value: any) {
const changedStyle = {
...this.props.mapStyle,
@@ -103,6 +121,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
const light = this.props.mapStyle.light || {};
const transition = this.props.mapStyle.transition || {};
const terrain = this.props.mapStyle.terrain || {} as TerrainSpecification;
const projection = this.props.mapStyle.projection || {} as ProjectionSpecification;
return <Modal
data-wd-key="modal:settings"
@@ -116,21 +135,21 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={latest.$root.name}
data-wd-key="modal:settings.name"
value={this.props.mapStyle.name}
onChange={this.changeStyleProperty.bind(this, "name")}
onChange={(value) => this.changeStyleProperty("name", value)}
/>
<FieldString
label={t("Owner")}
fieldSpec={{doc: t("Owner ID of the style. Used by Mapbox or future style APIs.")}}
data-wd-key="modal:settings.owner"
value={(this.props.mapStyle as any).owner}
onChange={this.changeStyleProperty.bind(this, "owner")}
onChange={(value) => this.changeStyleProperty("owner", value)}
/>
<FieldUrl
fieldSpec={latest.$root.sprite}
label={t("Sprite URL")}
data-wd-key="modal:settings.sprite"
value={this.props.mapStyle.sprite as string}
onChange={this.changeStyleProperty.bind(this, "sprite")}
onChange={(value) => this.changeStyleProperty("sprite", value)}
/>
<FieldUrl
@@ -138,7 +157,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={latest.$root.glyphs}
data-wd-key="modal:settings.glyphs"
value={this.props.mapStyle.glyphs as string}
onChange={this.changeStyleProperty.bind(this, "glyphs")}
onChange={(value) => this.changeStyleProperty("glyphs", value)}
/>
<FieldString
@@ -146,7 +165,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={fsa.maputnik.maptiler_access_token}
data-wd-key="modal:settings.maputnik:openmaptiles_access_token"
value={metadata["maputnik:openmaptiles_access_token"]}
onChange={onChangeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
onChange={(value) => onChangeMetadataProperty("maputnik:openmaptiles_access_token", value)}
/>
<FieldString
@@ -154,7 +173,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={fsa.maputnik.thunderforest_access_token}
data-wd-key="modal:settings.maputnik:thunderforest_access_token"
value={metadata["maputnik:thunderforest_access_token"]}
onChange={onChangeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
onChange={(value) => onChangeMetadataProperty("maputnik:thunderforest_access_token", value)}
/>
<FieldString
@@ -162,7 +181,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={fsa.maputnik.stadia_access_token}
data-wd-key="modal:settings.maputnik:stadia_access_token"
value={metadata["maputnik:stadia_access_token"]}
onChange={onChangeMetadataProperty.bind(this, "maputnik:stadia_access_token")}
onChange={(value) => onChangeMetadataProperty("maputnik:stadia_access_token", value)}
/>
<FieldString
@@ -170,7 +189,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={fsa.maputnik.locationiq_access_token}
data-wd-key="modal:settings.maputnik:locationiq_access_token"
value={metadata["maputnik:locationiq_access_token"]}
onChange={onChangeMetadataProperty.bind(this, "maputnik:locationiq_access_token")}
onChange={(value) => onChangeMetadataProperty("maputnik:locationiq_access_token", value)}
/>
<FieldArray
@@ -180,7 +199,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
type="number"
value={mapStyle.center || []}
default={[0, 0]}
onChange={this.changeStyleProperty.bind(this, "center")}
onChange={(value) => this.changeStyleProperty("center", value)}
/>
<FieldNumber
@@ -188,7 +207,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={latest.$root.zoom}
value={mapStyle.zoom}
default={0}
onChange={this.changeStyleProperty.bind(this, "zoom")}
onChange={(value) => this.changeStyleProperty("zoom", value)}
/>
<FieldNumber
@@ -196,7 +215,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={latest.$root.bearing}
value={mapStyle.bearing}
default={latest.$root.bearing.default}
onChange={this.changeStyleProperty.bind(this, "bearing")}
onChange={(value) => this.changeStyleProperty("bearing", value)}
/>
<FieldNumber
@@ -204,7 +223,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={latest.$root.pitch}
value={mapStyle.pitch}
default={latest.$root.pitch.default}
onChange={this.changeStyleProperty.bind(this, "pitch")}
onChange={(value) => this.changeStyleProperty("pitch", value)}
/>
<FieldEnum
@@ -214,7 +233,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
value={light.anchor as string}
options={Object.keys(latest.light.anchor.values)}
default={latest.light.anchor.default}
onChange={this.changeLightProperty.bind(this, "anchor")}
onChange={(value) => this.changeLightProperty("anchor", value)}
/>
<FieldColor
@@ -222,7 +241,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={latest.light.color}
value={light.color as string}
default={latest.light.color.default}
onChange={this.changeLightProperty.bind(this, "color")}
onChange={(value) => this.changeLightProperty("color", value)}
/>
<FieldNumber
@@ -230,7 +249,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={latest.light.intensity}
value={light.intensity as number}
default={latest.light.intensity.default}
onChange={this.changeLightProperty.bind(this, "intensity")}
onChange={(value) => this.changeLightProperty("intensity", value)}
/>
<FieldArray
@@ -240,7 +259,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
length={latest.light.position.length}
value={light.position as number[]}
default={latest.light.position.default}
onChange={this.changeLightProperty.bind(this, "position")}
onChange={(value) => this.changeLightProperty("position", value)}
/>
<FieldString
@@ -248,7 +267,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={latest.terrain.source}
data-wd-key="modal:settings.maputnik:terrain_source"
value={terrain.source}
onChange={this.changeTerrainProperty.bind(this, "source")}
onChange={(value) => this.changeTerrainProperty("source", value)}
/>
<FieldNumber
@@ -256,7 +275,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={latest.terrain.exaggeration}
value={terrain.exaggeration}
default={latest.terrain.exaggeration.default}
onChange={this.changeTerrainProperty.bind(this, "exaggeration")}
onChange={(value) => this.changeTerrainProperty("exaggeration", value)}
/>
<FieldNumber
@@ -264,7 +283,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={latest.transition.delay}
value={transition.delay}
default={latest.transition.delay.default}
onChange={this.changeTransitionProperty.bind(this, "delay")}
onChange={(value) => this.changeTransitionProperty("delay", value)}
/>
<FieldNumber
@@ -272,7 +291,20 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
fieldSpec={latest.transition.duration}
value={transition.duration}
default={latest.transition.duration.default}
onChange={this.changeTransitionProperty.bind(this, "duration")}
onChange={(value) => this.changeTransitionProperty("duration", value)}
/>
<FieldSelect
label={t("Projection")}
data-wd-key="modal:settings.projection"
options={[
["", "Undefined"],
["mercator", "Mercator"],
["globe", "Globe"],
["vertical-perspective", "Vertical Perspective"]
]}
value={projection?.type?.toString() || ""}
onChange={(value) => this.changeProjectionType(value)}
/>
<FieldSelect
@@ -284,7 +316,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
["ol", t("Open Layers (experimental)")],
]}
value={metadata["maputnik:renderer"] || "mlgljs"}
onChange={onChangeMetadataProperty.bind(this, "maputnik:renderer")}
onChange={(value) => onChangeMetadataProperty("maputnik:renderer", value)}
/>
</div>
</Modal>;