mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
feat(map): amap as map api provider and display template
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { effectivePlacesProvider, osmKeySuggestion } from './placesProvider'
|
||||
|
||||
/**
|
||||
* These mirror MapsService.resolvePlacesProvider() on the server, case for case.
|
||||
* The point of the mirror is honest notices, so every case where the notices
|
||||
* would lie — "add a Google key" on an install whose admin picked Amap — is
|
||||
* pinned here.
|
||||
*/
|
||||
describe('effectivePlacesProvider', () => {
|
||||
it('auto keeps Google when a Google key is configured, Amap key or not', () => {
|
||||
// The incumbent wins: an existing install must not move providers because
|
||||
// a new one became available.
|
||||
expect(effectivePlacesProvider({ placesProvider: 'auto', hasMapsKey: true, hasAmapKey: true })).toBe('google')
|
||||
expect(effectivePlacesProvider({ placesProvider: 'auto', hasMapsKey: true, hasAmapKey: false })).toBe('google')
|
||||
})
|
||||
|
||||
it('auto falls to Amap only when there is no Google key', () => {
|
||||
expect(effectivePlacesProvider({ placesProvider: 'auto', hasMapsKey: false, hasAmapKey: true })).toBe('amap')
|
||||
})
|
||||
|
||||
it('auto with neither key means OpenStreetMap', () => {
|
||||
expect(effectivePlacesProvider({ placesProvider: 'auto', hasMapsKey: false, hasAmapKey: false })).toBe('openstreetmap')
|
||||
})
|
||||
|
||||
it('an explicit amap choice wins even with a Google key configured', () => {
|
||||
expect(effectivePlacesProvider({ placesProvider: 'amap', hasMapsKey: true, hasAmapKey: true })).toBe('amap')
|
||||
})
|
||||
|
||||
it('an explicit google choice never silently uses Amap instead', () => {
|
||||
// Misconfigured means "answer with OSM", matching the server — not "bill
|
||||
// somebody else's provider".
|
||||
expect(effectivePlacesProvider({ placesProvider: 'google', hasMapsKey: false, hasAmapKey: true })).toBe('openstreetmap')
|
||||
})
|
||||
|
||||
it('openstreetmap ignores both keys', () => {
|
||||
expect(effectivePlacesProvider({ placesProvider: 'openstreetmap', hasMapsKey: true, hasAmapKey: true })).toBe('openstreetmap')
|
||||
})
|
||||
|
||||
it('a nonsense stored value degrades to auto rather than throwing', () => {
|
||||
expect(effectivePlacesProvider({ placesProvider: 'not-a-provider', hasMapsKey: false, hasAmapKey: true })).toBe('amap')
|
||||
expect(effectivePlacesProvider({ placesProvider: undefined, hasMapsKey: true, hasAmapKey: false })).toBe('google')
|
||||
})
|
||||
})
|
||||
|
||||
describe('osmKeySuggestion', () => {
|
||||
it('a chosen provider names its own key', () => {
|
||||
expect(osmKeySuggestion('google')).toBe('google')
|
||||
expect(osmKeySuggestion('amap')).toBe('amap')
|
||||
})
|
||||
|
||||
it('auto names both, because landing on OSM means neither key is set', () => {
|
||||
expect(osmKeySuggestion('auto')).toBe('any')
|
||||
})
|
||||
|
||||
it('an explicit OpenStreetMap choice gets no upsell — it is a decision, not a gap', () => {
|
||||
expect(osmKeySuggestion('openstreetmap')).toBeNull()
|
||||
// An unrecognised value behaves like auto: the reader degrades it to auto,
|
||||
// so the notice must not claim the admin chose OSM when they chose nothing.
|
||||
expect(osmKeySuggestion('garbage')).toBe('any')
|
||||
expect(osmKeySuggestion(undefined)).toBe('any')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Which provider place search will actually use, as far as the client can tell.
|
||||
*
|
||||
* This mirrors the server's MapsService.resolvePlacesProvider(): the instance's
|
||||
* places_provider choice, the keys the server reported for this caller, and the
|
||||
* same precedence. It exists so a notice can say the truth — "search is running
|
||||
* on OpenStreetMap", not "you lack a Google key" on an install whose admin
|
||||
* deliberately picked Amap.
|
||||
*
|
||||
* A mirror and not the source of truth on purpose: the server resolves keys per
|
||||
* request against the database, and only it knows for sure. If the two ever
|
||||
* disagree, the client shows a slightly wrong hint under a notice and nothing
|
||||
* else — search itself keeps asking the server.
|
||||
*/
|
||||
|
||||
export type PlacesProviderChoice = 'auto' | 'google' | 'amap' | 'openstreetmap'
|
||||
export type EffectiveSearchProvider = 'google' | 'amap' | 'openstreetmap'
|
||||
|
||||
export interface PlacesProviderState {
|
||||
/** The admin's places_provider choice; anything unrecognised means 'auto'. */
|
||||
placesProvider?: string | null
|
||||
/** Whether a Google key resolves for this caller (operator env, instance, own row). */
|
||||
hasMapsKey: boolean
|
||||
hasAmapKey: boolean
|
||||
}
|
||||
|
||||
export function effectivePlacesProvider(state: PlacesProviderState): EffectiveSearchProvider {
|
||||
const choice: PlacesProviderChoice =
|
||||
state.placesProvider === 'google' || state.placesProvider === 'amap' || state.placesProvider === 'openstreetmap'
|
||||
? state.placesProvider
|
||||
: 'auto'
|
||||
|
||||
if (choice === 'openstreetmap') return 'openstreetmap'
|
||||
if (choice === 'google') return state.hasMapsKey ? 'google' : 'openstreetmap'
|
||||
if (choice === 'amap') return state.hasAmapKey ? 'amap' : 'openstreetmap'
|
||||
|
||||
// auto prefers Google, the incumbent: an existing install must not move to a
|
||||
// different provider, with a different bill, just because Amap appeared.
|
||||
if (state.hasMapsKey) return 'google'
|
||||
if (state.hasAmapKey) return 'amap'
|
||||
return 'openstreetmap'
|
||||
}
|
||||
|
||||
/**
|
||||
* Which key the "still on OpenStreetMap" upsell should suggest.
|
||||
*
|
||||
* A chosen provider names the key that belongs to it — telling an admin who
|
||||
* picked Amap to add a Google key is advice they already declined. `auto` with
|
||||
* no key at all is the only case where both are worth naming.
|
||||
*
|
||||
* Null when the admin explicitly chose OpenStreetMap: that is a decision, not a
|
||||
* gap to fill, and the notices stay quiet rather than nag.
|
||||
*/
|
||||
export type KeySuggestion = 'google' | 'amap' | 'any'
|
||||
|
||||
export function osmKeySuggestion(placesProvider?: string | null): KeySuggestion | null {
|
||||
switch (placesProvider) {
|
||||
case 'google':
|
||||
return 'google'
|
||||
case 'amap':
|
||||
return 'amap'
|
||||
case 'openstreetmap':
|
||||
return null
|
||||
// auto resolving to OSM means neither key is configured (a Google key would
|
||||
// have won), so both are honest suggestions.
|
||||
default:
|
||||
return 'any'
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,25 @@ export function isVectorStyle(url: string | null | undefined): boolean {
|
||||
return /^https?:\/\//i.test(u) || u.startsWith('mapbox://')
|
||||
}
|
||||
|
||||
/**
|
||||
* Tiles drawn in GCJ-02 rather than WGS-84.
|
||||
*
|
||||
* Amap serves its raster tiles from `*.is.autonavi.com`, and they are shifted
|
||||
* from the WGS-84 coordinates everything else in TREK uses — by 100 to 800 m
|
||||
* depending on where in China you are. A map on one of these has to project
|
||||
* through components/Map/gcj02Crs.ts or every marker lands on the wrong street.
|
||||
*
|
||||
* Matched on the host, not on the preset constants, so a self-hosted proxy in
|
||||
* front of the same tiles still gets the right CRS as long as the hostname
|
||||
* survives — and so a template a user typed in by hand does too.
|
||||
*/
|
||||
const GCJ02_TILE_HOST = /(^|\.)is\.autonavi\.com$/i
|
||||
|
||||
export function isGcj02Basemap(url: string | null | undefined): boolean {
|
||||
if (!url) return false
|
||||
return GCJ02_TILE_HOST.test(templateHost(url))
|
||||
}
|
||||
|
||||
/**
|
||||
* What a map should draw, given the user's template and the app's default.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user