Add LocationIQ as supported map provider (#1293)

- Add LocationIQ access token field to settings and export modals
  - Include LocationIQ Streets style in gallery
  - Support automatic token replacement for LocationIQ URLs
  - Add LocationIQ tileset configuration
  - Include translations for all supported languages

## Launch Checklist
 - [x] Briefly describe the changes in this PR.
 - [x] Write tests for all new functionality.
 - [x] Add an entry to `CHANGELOG.md` under the `## main` section.
This commit is contained in:
Gopi Aravind
2025-08-17 13:36:26 +05:30
committed by GitHub
parent 728de3aed6
commit e2e29d7f5e
16 changed files with 69 additions and 1 deletions
+1
View File
@@ -16,6 +16,7 @@
- Remove usage of legacy `childContextTypes` API - Remove usage of legacy `childContextTypes` API
- Refactor Field components to use arrow function syntax - Refactor Field components to use arrow function syntax
- Replace react-autocomplete with Downshift in the autocomplete component - Replace react-autocomplete with Downshift in the autocomplete component
- Add LocationIQ as supported map provider with access token field and gallery style
- _...Add new stuff here..._ - _...Add new stuff here..._
### 🐞 Bug fixes ### 🐞 Bug fixes
+12
View File
@@ -224,6 +224,18 @@ describe("modals", () => {
).shouldInclude({ "maputnik:stadia_access_token": apiKey }); ).shouldInclude({ "maputnik:stadia_access_token": apiKey });
}); });
it("locationiq access token", () => {
const apiKey = "testing123";
when.setValue(
"modal:settings.maputnik:locationiq_access_token",
apiKey
);
when.click("modal:settings.name");
then(
get.styleFromLocalStorage().then((style) => style.metadata)
).shouldInclude({ "maputnik:locationiq_access_token": apiKey });
});
it("style renderer", () => { it("style renderer", () => {
cy.on("uncaught:exception", () => false); // this is due to the fact that this is an invalid style for openlayers cy.on("uncaught:exception", () => false); // this is due to the fact that this is an invalid style for openlayers
when.select("modal:settings.maputnik:renderer", "ol"); when.select("modal:settings.maputnik:renderer", "ol");
+7
View File
@@ -46,6 +46,7 @@ function setFetchAccessToken(url: string, mapStyle: StyleSpecification) {
const matchesTilehosting = url.match(/\.tilehosting\.com/); const matchesTilehosting = url.match(/\.tilehosting\.com/);
const matchesMaptiler = url.match(/\.maptiler\.com/); const matchesMaptiler = url.match(/\.maptiler\.com/);
const matchesThunderforest = url.match(/\.thunderforest\.com/); const matchesThunderforest = url.match(/\.thunderforest\.com/);
const matchesLocationIQ = url.match(/\.locationiq\.com/);
if (matchesTilehosting || matchesMaptiler) { if (matchesTilehosting || matchesMaptiler) {
const accessToken = style.getAccessToken("openmaptiles", mapStyle, {allowFallback: true}) const accessToken = style.getAccessToken("openmaptiles", mapStyle, {allowFallback: true})
if (accessToken) { if (accessToken) {
@@ -58,6 +59,12 @@ function setFetchAccessToken(url: string, mapStyle: StyleSpecification) {
return url.replace('{key}', accessToken) return url.replace('{key}', accessToken)
} }
} }
else if (matchesLocationIQ) {
const accessToken = style.getAccessToken("locationiq", mapStyle, {allowFallback: true})
if (accessToken) {
return url.replace('{key}', accessToken)
}
}
else { else {
return url; return url;
} }
+6
View File
@@ -186,6 +186,12 @@ class ModalExportInternal extends React.Component<ModalExportInternalProps> {
value={(this.props.mapStyle.metadata || {} as any)['maputnik:stadia_access_token']} value={(this.props.mapStyle.metadata || {} as any)['maputnik:stadia_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:stadia_access_token")} onChange={this.changeMetadataProperty.bind(this, "maputnik:stadia_access_token")}
/> />
<FieldString
label={fsa.maputnik.locationiq_access_token.label}
fieldSpec={fsa.maputnik.locationiq_access_token}
value={(this.props.mapStyle.metadata || {} as any)['maputnik:locationiq_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:locationiq_access_token")}
/>
</div> </div>
<div className="maputnik-modal-export-buttons"> <div className="maputnik-modal-export-buttons">
+8
View File
@@ -164,6 +164,14 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
onChange={onChangeMetadataProperty.bind(this, "maputnik:stadia_access_token")} onChange={onChangeMetadataProperty.bind(this, "maputnik:stadia_access_token")}
/> />
<FieldString
label={fsa.maputnik.locationiq_access_token.label}
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")}
/>
<FieldArray <FieldArray
label={t("Center")} label={t("Center")}
fieldSpec={latest.$root.center} fieldSpec={latest.$root.center}
+6
View File
@@ -29,6 +29,12 @@
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/dark-matter-gl-style@v1.9/style.json", "url": "https://cdn.jsdelivr.net/gh/openmaptiles/dark-matter-gl-style@v1.9/style.json",
"thumbnail": "https://maputnik.github.io/thumbnails/dark-matter.png" "thumbnail": "https://maputnik.github.io/thumbnails/dark-matter.png"
}, },
{
"id": "locationiq-streets",
"title": "LocationIQ Streets",
"url": "https://tiles.locationiq.com/v3/streets/vector.json?key={key}",
"thumbnail": "https://static-assets.locationiq.com/maputnik/locationiq-streets.png"
},
{ {
"id": "maptiler-basic-gl-style", "id": "maptiler-basic-gl-style",
"title": "MapTiler Basic", "title": "MapTiler Basic",
+5
View File
@@ -18,5 +18,10 @@
"type": "vector", "type": "vector",
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/v2/data/vector/open-zoomstack/config.json", "url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/v2/data/vector/open-zoomstack/config.json",
"title": "OS Open Zoomstack v2" "title": "OS Open Zoomstack v2"
},
"locationiq": {
"type": "vector",
"url": "https://tiles.locationiq.com/v3/pbf/tiles.json?key={key}",
"title": "LocationIQ"
} }
} }
+2 -1
View File
@@ -1,4 +1,5 @@
{ {
"openmaptiles": "get_your_own_OpIi9ZULNHzrESv6T2vL", "openmaptiles": "get_your_own_OpIi9ZULNHzrESv6T2vL",
"thunderforest": "b71f7f0ba4064f5eb9e903859a9cf5c6" "thunderforest": "b71f7f0ba4064f5eb9e903859a9cf5c6",
"locationiq": "pk.put_your_api_key_here7bb23dffeb4"
} }
+6
View File
@@ -20,6 +20,12 @@ const spec = (t: TFunction) => ({
docUrl: "https://docs.stadiamaps.com/authentication/", docUrl: "https://docs.stadiamaps.com/authentication/",
docUrlLinkText: t("Learn More") docUrlLinkText: t("Learn More")
}, },
locationiq_access_token: {
label: t("LocationIQ Access Token"),
doc: t("Public access token for LocationIQ services."),
docUrl: "https://docs.locationiq.com/docs/maps",
docUrlLinkText: t("Learn More")
},
style_renderer: { style_renderer: {
label: t("Style Renderer"), label: t("Style Renderer"),
doc: t("Choose the default Maputnik renderer for this style.") doc: t("Choose the default Maputnik renderer for this style.")
+4
View File
@@ -79,6 +79,9 @@ function replaceSourceAccessToken(mapStyle: StyleSpecification, sourceName: stri
// so we need to check the source URL. // so we need to check the source URL.
authSourceName = "stadia" authSourceName = "stadia"
} }
else if (("url" in source) && source.url?.match(/\.locationiq\.com/)) {
authSourceName = "locationiq"
}
const accessToken = getAccessToken(authSourceName, mapStyle, opts) const accessToken = getAccessToken(authSourceName, mapStyle, opts)
@@ -138,6 +141,7 @@ function stripAccessTokens(mapStyle: StyleSpecification) {
delete changedMetadata['maputnik:openmaptiles_access_token']; delete changedMetadata['maputnik:openmaptiles_access_token'];
delete changedMetadata['maputnik:thunderforest_access_token']; delete changedMetadata['maputnik:thunderforest_access_token'];
delete changedMetadata['maputnik:stadia_access_token']; delete changedMetadata['maputnik:stadia_access_token'];
delete changedMetadata['maputnik:locationiq_access_token'];
return { return {
...mapStyle, ...mapStyle,
metadata: changedMetadata metadata: changedMetadata
+2
View File
@@ -183,6 +183,8 @@
"Public access token for Thunderforest services.": "Öffentlicher Zugriffstoken für Thunderforest-Dienste.", "Public access token for Thunderforest services.": "Öffentlicher Zugriffstoken für Thunderforest-Dienste.",
"Stadia Maps API Key": "Stadia Maps API-Schlüssel", "Stadia Maps API Key": "Stadia Maps API-Schlüssel",
"API key for Stadia Maps.": "API-Schlüssel für Stadia Maps.", "API key for Stadia Maps.": "API-Schlüssel für Stadia Maps.",
"LocationIQ Access Token": "LocationIQ Zugriffstoken",
"Public access token for LocationIQ services.": "Öffentlicher Zugriffstoken für LocationIQ-Dienste.",
"Style Renderer": "Stil-Renderer", "Style Renderer": "Stil-Renderer",
"Choose the default Maputnik renderer for this style.": "Wähle den Standard-Renderer für diesen Stil aus.", "Choose the default Maputnik renderer for this style.": "Wähle den Standard-Renderer für diesen Stil aus.",
"Paint properties": "Darstellungseigenschaften", "Paint properties": "Darstellungseigenschaften",
+2
View File
@@ -183,6 +183,8 @@
"Public access token for Thunderforest services.": "Jeton d'accès public pour les services Thunderforest.", "Public access token for Thunderforest services.": "Jeton d'accès public pour les services Thunderforest.",
"Stadia Maps API Key": "Clé d'API Stadia Maps", "Stadia Maps API Key": "Clé d'API Stadia Maps",
"API key for Stadia Maps.": "Clé d'API pour Stadia Maps.", "API key for Stadia Maps.": "Clé d'API pour Stadia Maps.",
"LocationIQ Access Token": "Jeton d'accès LocationIQ",
"Public access token for LocationIQ services.": "Jeton d'accès public pour les services LocationIQ.",
"Style Renderer": "Moteur de rendu pour le style", "Style Renderer": "Moteur de rendu pour le style",
"Choose the default Maputnik renderer for this style.": "Choisissez le moteur de rendu Maputnik par défaut pour ce style.", "Choose the default Maputnik renderer for this style.": "Choisissez le moteur de rendu Maputnik par défaut pour ce style.",
"Language": "Langue", "Language": "Langue",
+2
View File
@@ -183,6 +183,8 @@
"Public access token for Thunderforest services.": "Public access token for Thunderforest services.", "Public access token for Thunderforest services.": "Public access token for Thunderforest services.",
"Stadia Maps API Key": "Stadia Maps API Key", "Stadia Maps API Key": "Stadia Maps API Key",
"API key for Stadia Maps.": "API key for Stadia Maps", "API key for Stadia Maps.": "API key for Stadia Maps",
"LocationIQ Access Token": "LocationIQ Access Token",
"Public access token for LocationIQ services.": "Public access token for LocationIQ services.",
"Style Renderer": "צייר הסטייל", "Style Renderer": "צייר הסטייל",
"Choose the default Maputnik renderer for this style.": "בחירת צייר ברירת המחדל של מפוטניק עבור הסטייל הזה", "Choose the default Maputnik renderer for this style.": "בחירת צייר ברירת המחדל של מפוטניק עבור הסטייל הזה",
"Language": "שפה", "Language": "שפה",
+2
View File
@@ -183,6 +183,8 @@
"Public access token for Thunderforest services.": "Token di accesso pubblico per i servizi Thunderforest.", "Public access token for Thunderforest services.": "Token di accesso pubblico per i servizi Thunderforest.",
"Stadia Maps API Key": "Chiave API di Stadia Maps.", "Stadia Maps API Key": "Chiave API di Stadia Maps.",
"API key for Stadia Maps.": "Chiave API per Stadia Maps.", "API key for Stadia Maps.": "Chiave API per Stadia Maps.",
"LocationIQ Access Token": "Token di accesso LocationIQ",
"Public access token for LocationIQ services.": "Token di accesso pubblico per i servizi LocationIQ.",
"Style Renderer": "Renderer dello stile", "Style Renderer": "Renderer dello stile",
"Choose the default Maputnik renderer for this style.": "Scegli il renderer predefinito di Maputnik per questo stile.", "Choose the default Maputnik renderer for this style.": "Scegli il renderer predefinito di Maputnik per questo stile.",
"Language": "Lingua", "Language": "Lingua",
+2
View File
@@ -183,6 +183,8 @@
"Public access token for Thunderforest services.": "Thunderforest サービスの公開用アクセストークン", "Public access token for Thunderforest services.": "Thunderforest サービスの公開用アクセストークン",
"Stadia Maps API Key": "Stadia Maps API キー", "Stadia Maps API Key": "Stadia Maps API キー",
"API key for Stadia Maps.": "Stadia Maps の API キー", "API key for Stadia Maps.": "Stadia Maps の API キー",
"LocationIQ Access Token": "LocationIQ アクセストークン",
"Public access token for LocationIQ services.": "LocationIQ サービス用のパブリックアクセストークン。",
"Style Renderer": "スタイルレンダラ", "Style Renderer": "スタイルレンダラ",
"Choose the default Maputnik renderer for this style.": "このスタイルのデフォルトの Maputnik レンダラを選択してください", "Choose the default Maputnik renderer for this style.": "このスタイルのデフォルトの Maputnik レンダラを選択してください",
"Language": "言語", "Language": "言語",
+2
View File
@@ -183,6 +183,8 @@
"Public access token for Thunderforest services.": "Thunderforest 服务的公共访问令牌。", "Public access token for Thunderforest services.": "Thunderforest 服务的公共访问令牌。",
"Stadia Maps API Key": "Stadia Maps API 密钥", "Stadia Maps API Key": "Stadia Maps API 密钥",
"API key for Stadia Maps.": "Stadia Maps 的 API 密钥", "API key for Stadia Maps.": "Stadia Maps 的 API 密钥",
"LocationIQ Access Token": "LocationIQ 访问令牌",
"Public access token for LocationIQ services.": "LocationIQ 服务的公共访问令牌。",
"Style Renderer": "样式渲染器", "Style Renderer": "样式渲染器",
"Choose the default Maputnik renderer for this style.": "为这种样式选择默认的Maputnik渲染器。", "Choose the default Maputnik renderer for this style.": "为这种样式选择默认的Maputnik渲染器。",
"Language": "语言", "Language": "语言",