basemap.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /**
  2. * 底图计划(票 29):把配置里的 `map.basemap` 翻译成「用哪一类 Cesium 影像源、什么 URL」。
  3. *
  4. * 纯函数、**不 import cesium** —— 引擎侧(services/cesiumEngine.js)照着这份计划建 provider,
  5. * 逻辑断言脚本因此能脱浏览器把每种来源(含天地图模板拼装、缺 tk 的失败态)都测到。
  6. *
  7. * 为什么单独一层:票 27 故意 `baseLayer: false`(底图未配置就不伪造地理配准),
  8. * 票 21 负责地形 / 3D Tiles / 倾斜摄影与地面-管线视图切换;底图影像这一层独立成票。
  9. */
  10. /** 底图来源(配置里 `map.basemap.source` 的取值)。 */
  11. export const BASEMAP_SOURCE = {
  12. NONE: 'none',
  13. NATURAL_EARTH: 'natural-earth',
  14. ARCGIS_IMAGERY: 'arcgis-imagery',
  15. XYZ: 'xyz',
  16. TIANDITU: 'tianditu',
  17. }
  18. /** 底图状态(写进宿主 `data-map-basemap`,也是 getLifecycleStats().basemap 的取值)。 */
  19. export const BASEMAP_STATE = {
  20. NOT_CONFIGURED: 'not-configured',
  21. NATURAL_EARTH: 'natural-earth',
  22. ARCGIS_IMAGERY: 'arcgis-imagery',
  23. XYZ: 'xyz',
  24. TIANDITU: 'tianditu',
  25. INVALID: 'invalid',
  26. }
  27. /**
  28. * ArcGIS 官方世界影像:免费、免 key(署名即可),模板顺序是 `{z}/{y}/{x}`。
  29. * 注意本机实测**连不上**(外网被挡),离线环境请用 `natural-earth`。
  30. */
  31. export const ARCGIS_WORLD_IMAGERY_URL =
  32. 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'
  33. /** Cesium 自带的离线影像(随 `Build/Cesium/Assets` 一起进产物,不需要账号或外网)。 */
  34. export const NATURAL_EARTH_TMS = 'Assets/Textures/NaturalEarthII'
  35. /** 天地图子域(生产直连时用;开发走仓库里已有的 `/tianditu.map` 代理,单主机不加子域)。 */
  36. export const TIANDITU_SUBDOMAINS = ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7']
  37. export const TIANDITU_LAYERS = {
  38. img: '影像底图',
  39. vec: '矢量底图',
  40. ter: '地形晕渲',
  41. }
  42. /**
  43. * 天地图 WMTS 瓦片模板(tk 拼在查询串里;Cesium 负责替换 `{s}`/`{z}`/`{x}`/`{y}`)。
  44. * @param {string} base 形如 `/tianditu.map`(开发代理)或 `https://t{s}.tianditu.gov.cn`(生产)
  45. * @param {string} token 天地图 tk
  46. * @param {string} layer `img` / `vec` / `ter`
  47. */
  48. export function tiandituTileUrl(base, token, layer = 'img') {
  49. const root = String(base || '').replace(/\/+$/, '')
  50. return `${root}/${layer}_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=${layer}&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${token}`
  51. }
  52. function basePlan(state, source, label, extra = {}) {
  53. return { state, source, label, ready: false, kind: '', url: '', subdomains: [], minimumLevel: null, maximumLevel: null, credit: '', issue: '', ...extra }
  54. }
  55. /**
  56. * 配置 → 底图计划。
  57. * @param {object} mapConfig 归一化后的 `config.map`
  58. * @param {object} [options]
  59. * @param {boolean} [options.dev] 开发态:天地图走仓库自带的 `/tianditu.map` 代理
  60. * @returns {{state: string, source: string, label: string, ready: boolean, kind: string, url: string,
  61. * subdomains: string[], minimumLevel: number|null, maximumLevel: number|null, credit: string, issue: string}}
  62. */
  63. export function planBasemap(mapConfig = {}, options = {}) {
  64. const map = mapConfig || {}
  65. const basemap = map.basemap || {}
  66. const source = basemap.source || BASEMAP_SOURCE.NONE
  67. if (source === BASEMAP_SOURCE.NONE) {
  68. return basePlan(BASEMAP_STATE.NOT_CONFIGURED, source, '底图未配置')
  69. }
  70. if (source === BASEMAP_SOURCE.NATURAL_EARTH) {
  71. return basePlan(BASEMAP_STATE.NATURAL_EARTH, source, '内置离线影像 Natural Earth II', {
  72. ready: true,
  73. kind: 'tms',
  74. url: NATURAL_EARTH_TMS,
  75. })
  76. }
  77. if (source === BASEMAP_SOURCE.ARCGIS_IMAGERY) {
  78. return basePlan(BASEMAP_STATE.ARCGIS_IMAGERY, source, 'ArcGIS 全球影像(免费免 key)', {
  79. ready: true,
  80. kind: 'xyz',
  81. url: ARCGIS_WORLD_IMAGERY_URL,
  82. credit: 'Esri, Maxar, Earthstar Geographics',
  83. })
  84. }
  85. if (source === BASEMAP_SOURCE.XYZ) {
  86. if (!basemap.url) {
  87. return basePlan(BASEMAP_STATE.INVALID, source, '底图配置无效', { issue: 'xyz 底图缺少 url 模板' })
  88. }
  89. return basePlan(BASEMAP_STATE.XYZ, source, '自定义瓦片(XYZ)', {
  90. ready: true,
  91. kind: 'xyz',
  92. url: basemap.url,
  93. subdomains: basemap.subdomains || [],
  94. minimumLevel: basemap.minimumLevel,
  95. maximumLevel: basemap.maximumLevel,
  96. credit: basemap.credit || '',
  97. })
  98. }
  99. if (source === BASEMAP_SOURCE.TIANDITU) {
  100. const token = basemap.token || (map.tianditu && map.tianditu.token) || ''
  101. if (!token) {
  102. return basePlan(BASEMAP_STATE.INVALID, source, '底图配置无效', {
  103. issue: '天地图底图缺少 tk(填 map.tianditu.token)',
  104. })
  105. }
  106. const requested = (map.tianditu && map.tianditu.layer) || basemap.layer || 'img'
  107. const layer = TIANDITU_LAYERS[requested] ? requested : 'img'
  108. const dev = options.dev === true
  109. return basePlan(BASEMAP_STATE.TIANDITU, source, `天地图${TIANDITU_LAYERS[layer]}`, {
  110. ready: true,
  111. kind: 'xyz',
  112. url: tiandituTileUrl(dev ? '/tianditu.map' : 'https://t{s}.tianditu.gov.cn', token, layer),
  113. subdomains: dev ? [] : TIANDITU_SUBDOMAINS,
  114. credit: '天地图',
  115. })
  116. }
  117. return basePlan(BASEMAP_STATE.INVALID, source, '底图配置无效', { issue: `未知的底图来源 ${source}` })
  118. }
  119. /** 引擎状态浮标上的底图措辞(未配置时保持票 27 的原话)。 */
  120. export function basemapStatusText(plan) {
  121. const current = plan || planBasemap({})
  122. if (current.ready) return `底图:${current.label}`
  123. if (current.state === BASEMAP_STATE.INVALID) return `底图配置无效(${current.issue})· 回退临时影像`
  124. return '底图未配置(正式底图见票 21)'
  125. }