├── src ├── server │ ├── collection.ts │ ├── geocloud.ts │ ├── tiles │ │ └── bing.ts │ ├── jilin1.ts │ └── tianditu.ts ├── build.ts ├── maps │ ├── bing.ts │ ├── tianditu │ │ ├── jilin.ts │ │ ├── jiangsu_xuzhou.ts │ │ ├── fujian.ts │ │ ├── beijing.ts │ │ └── jiangsu.ts │ ├── shipxy.ts │ ├── yandex.ts │ ├── google_map.ts │ ├── debug.ts │ ├── geoq.ts │ ├── amap.ts │ ├── gggis.ts │ ├── esri.ts │ ├── collection.ts │ ├── tianditu_sd.ts │ ├── jilin1.ts │ ├── tianditu.ts │ ├── geocloud.ts │ └── osm.ts ├── server.ts └── maps.ts ├── deno.json ├── README.md ├── .github └── workflows │ └── deploy.yml ├── LICENSE ├── deno.lock └── .gitignore /src/server/collection.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "@hono/hono"; 2 | import { collection } from "../maps/collection.ts"; 3 | 4 | export const router = new Hono(); 5 | 6 | router.get("/", (c) => { 7 | c.header("Content-Type", "text/xml;charset=UTF-8"); 8 | return c.body(collection); 9 | }); 10 | -------------------------------------------------------------------------------- /src/server/geocloud.ts: -------------------------------------------------------------------------------- 1 | import { geocloud_cap } from "../maps/geocloud.ts"; 2 | import { Hono } from "@hono/hono"; 3 | 4 | export const router = new Hono(); 5 | 6 | router.get("/", (c) => { 7 | const token = c.req.header("tk") || c.req.query("tk"); 8 | if (token) { 9 | c.header("Content-Type", "text/xml;charset=UTF-8"); 10 | return c.body(geocloud_cap(token)); 11 | } else { 12 | return c.text("Token is required", 400); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "dev": "deno run -A --watch ./src/server.ts", 4 | "build": "deno run -A ./src/build.ts", 5 | "deploy": "deployctl deploy" 6 | }, 7 | "deploy": { 8 | "project": "148f8cbf-1faa-48ff-ad8e-bd6cb6c7c271", 9 | "exclude": ["**/node_modules", "**/.git"], 10 | "include": [], 11 | "entrypoint": "src\\server.ts" 12 | }, 13 | "imports": { 14 | "@hono/hono": "jsr:@hono/hono@^4.8.5", 15 | "@liuxspro/capgen": "jsr:@liuxspro/capgen@^0.2.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/build.ts: -------------------------------------------------------------------------------- 1 | import { maps } from "./maps.ts"; 2 | 3 | async function create_dist_dir() { 4 | try { 5 | await Deno.mkdir("dist", { recursive: true }); 6 | } catch (err) { 7 | if (err instanceof Deno.errors.AlreadyExists) { 8 | console.log("dist 文件夹已存在"); 9 | } else { 10 | console.error("发生错误:", err); 11 | } 12 | } 13 | } 14 | 15 | Object.entries(maps).forEach(async ([key, value]) => { 16 | await create_dist_dir(); 17 | await Deno.writeTextFile(`./dist/${key}.xml`, value); 18 | }); 19 | console.log("Done!"); 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WMTS Maps 2 | 3 | 将 XYZ 瓦片转换为 WMTS 服务 4 | 5 | ## 部署 6 | 7 | ``` 8 | deployctl deploy --entrypoint .\src\server.ts 9 | ``` 10 | 11 | ## Maps 12 | 13 | ### 常用底图合集 `/collection` 14 | 15 | 常用底图合集 All in One : `/dist/collection.xml` 16 | 17 | ### 常用底图分集 18 | 19 | - 高德 : `/dist/amap.xml` 20 | - esri : `/dist/esri.xml` 21 | - OSM : `/dist/osm.xml` 22 | - 谷歌 :`/dist/google.xml` 23 | - 谷谷 : `/dist/gggis.xml` 24 | - 天地图江苏: `/dist/tianditu_js.xml` 25 | - 天地图北京: `/dist/beijing.xml` 26 | 27 | ### 天地图 自行设置tk `/tianditu` 28 | 29 | 天地图需要 token (key),请求`/tianditu`时, 30 | 可通过添加查询参数`?tdt=`或者在 header 标头中添加`tdt`参数来设置 token 31 | -------------------------------------------------------------------------------- /src/maps/bing.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Capabilities, 3 | MapLayer, 4 | mercator_bbox, 5 | Service, 6 | web_mercator_quad, 7 | } from "@liuxspro/capgen"; 8 | 9 | const bing = new MapLayer( 10 | "Bing Virtual Earth", 11 | "Bing Virtual Earth", 12 | "bing", 13 | mercator_bbox, 14 | web_mercator_quad.clone(), 15 | "https://wmts.liuxs.pro/tile/bing/{z}/{x}/{y}", 16 | "image/jpeg", 17 | ); 18 | 19 | export const layers = [bing]; 20 | 21 | export const service: Service = { 22 | title: "Bing Virtual Earth", 23 | abstract: "Bing Virtual Earth", 24 | keywords: ["Bing Virtual Earth"], 25 | }; 26 | 27 | export const cap = new Capabilities(service, layers).xml; 28 | -------------------------------------------------------------------------------- /src/maps/tianditu/jilin.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Capabilities, 3 | cgcs2000_quad, 4 | default_service, 5 | GeoPoint, 6 | MapLayer, 7 | } from "@liuxspro/capgen"; 8 | 9 | const jl_bbox: [GeoPoint, GeoPoint] = [ 10 | { lon: 121.149676, lat: 39.533551 }, // 西南角 (LowerCorner) 11 | { lon: 131.791571, lat: 47.134905 }, // 东北角 (UpperCorner) 12 | ]; 13 | 14 | const jl_2024 = new MapLayer( 15 | "吉林2024影像", 16 | "吉林2024影像", 17 | "jl_2024", 18 | jl_bbox, 19 | cgcs2000_quad.clone(), 20 | "https://jilin.tianditu.gov.cn/ime-cloud/rest/JLS_DOM202406/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=JLS_DOM202406&STYLE=default&FORMAT=image%2Fjpgpng&TILEMATRIXSET=default028mm&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}", 21 | "image/jpgpng", 22 | ); 23 | 24 | export const cap = new Capabilities(default_service, [jl_2024]).xml; 25 | -------------------------------------------------------------------------------- /src/maps/shipxy.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Capabilities, 3 | GeoPoint, 4 | MapLayer, 5 | Service, 6 | world_mercator_quad, 7 | } from "@liuxspro/capgen"; 8 | 9 | export const world_mercator_bbox: [GeoPoint, GeoPoint] = [ 10 | { lon: -180.0, lat: -85.08405903 }, // 西南角 (LowerCorner) 11 | { lon: 180.0, lat: 85.08405903 }, // 东北角 (UpperCorner) 12 | ]; 13 | 14 | const haitu = new MapLayer( 15 | "船讯网 - 海图", 16 | "船讯网 - 海图", 17 | "shipxy_ht", 18 | world_mercator_bbox, 19 | world_mercator_quad.clone().setZoom(1, 17), 20 | "https://m12.shipxy.com/tile.c?l=Na&m=o&x={x}&y={y}&z={z}", 21 | "image/png", 22 | ); 23 | 24 | export const layers = [haitu]; 25 | 26 | export const service: Service = { 27 | title: "船讯网", 28 | abstract: "船讯网", 29 | keywords: ["船讯网", "海图"], 30 | }; 31 | 32 | export const cap = new Capabilities(service, layers).xml; 33 | -------------------------------------------------------------------------------- /src/server/tiles/bing.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "@hono/hono"; 2 | 3 | function tileToQuadkey(x: number, y: number, z: number) { 4 | if (z === 0) { 5 | return ""; // 缩放级别0的quadkey为空字符串 6 | } 7 | let quadkey = ""; 8 | for (let i = z; i > 0; i--) { 9 | let digit = 0; 10 | const mask = 1 << (i - 1); 11 | if ((x & mask) !== 0) digit += 1; 12 | if ((y & mask) !== 0) digit += 2; 13 | quadkey += digit; 14 | } 15 | return quadkey; 16 | } 17 | 18 | export const router = new Hono(); 19 | 20 | router.get("/tile/bing/:z/:x/:y", (c) => { 21 | const { z, x, y } = c.req.param(); 22 | const quadkey = tileToQuadkey(parseInt(x), parseInt(y), parseInt(z)); 23 | const redirect = 24 | `https://ecn.t3.tiles.virtualearth.net/tiles/a${quadkey}.jpeg?g=0&dir=dir_n`; 25 | 26 | // 重定向至 redict_url 27 | return c.redirect(redirect); 28 | }); 29 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | branches: main 5 | pull_request: 6 | branches: main 7 | 8 | jobs: 9 | deploy: 10 | name: Deploy 11 | runs-on: ubuntu-latest 12 | 13 | permissions: 14 | id-token: write # Needed for auth with Deno Deploy 15 | contents: read # Needed to clone the repository 16 | 17 | steps: 18 | - name: Clone repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Install Deno 22 | uses: denoland/setup-deno@v2 23 | with: 24 | deno-version: v2.x 25 | 26 | - name: Build step 27 | run: "deno task build" 28 | 29 | - name: Upload to Deno Deploy 30 | uses: denoland/deployctl@v1 31 | with: 32 | project: "wmts" 33 | entrypoint: "src/server.ts" 34 | root: "" 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/maps/yandex.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Capabilities, 3 | GeoPoint, 4 | MapLayer, 5 | Service, 6 | world_mercator_quad, 7 | } from "@liuxspro/capgen"; 8 | 9 | export const world_mercator_bbox: [GeoPoint, GeoPoint] = [ 10 | { lon: -180.0, lat: -85.08405903 }, // 西南角 (LowerCorner) 11 | { lon: 180.0, lat: 85.08405903 }, // 东北角 (UpperCorner) 12 | ]; 13 | 14 | const yandex_sat = new MapLayer( 15 | "Yandex - Satellite", 16 | "Yandex - Satellite", 17 | "yandex_sat", 18 | world_mercator_bbox, 19 | world_mercator_quad.clone(), 20 | "https://sat02.maps.yandex.net/tiles?l=sat&x={x}&y={y}&z={z}", 21 | "image/jpeg", 22 | ); 23 | 24 | export const layers = [yandex_sat]; 25 | 26 | export const service: Service = { 27 | title: "Yandex", 28 | abstract: "Yandex - Satellite", 29 | keywords: ["Yandex", "Satellite"], 30 | }; 31 | 32 | export const cap = new Capabilities(service, layers).xml; 33 | -------------------------------------------------------------------------------- /src/server/jilin1.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "@hono/hono"; 2 | import { jl1_cap } from "../maps/jilin1.ts"; 3 | 4 | export const router = new Hono(); 5 | 6 | router.get("/", (c) => { 7 | const tk_name = "tk"; // 如果用tk, arcgis 设置的自定义参数会导致瓦片URL tk 重复 8 | const tdt_tk = c.req.header(tk_name) || c.req.query(tk_name); 9 | if (tdt_tk) { 10 | // 创建图层副本并设置token 11 | let token = tdt_tk; 12 | // 处理请求参数后携带 `/1.0.0/WMTSCapabilities.xml`的情况(arcgis 10.2) 13 | if (tdt_tk.includes("/")) { 14 | token = tdt_tk.split("/")[0]; 15 | } 16 | // 处理请求参数后携带 `?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetCapabilities`的情况(arcgis 10.2) 17 | if (tdt_tk.includes("?")) { 18 | token = tdt_tk.split("?")[0]; 19 | } 20 | c.header("Content-Type", "text/xml;charset=UTF-8"); 21 | return c.body(jl1_cap(token)); 22 | } else { 23 | return c.text("JiLin1 token is required", 400); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "@hono/hono"; 2 | import { serveStatic } from "@hono/hono/deno"; 3 | import { trimTrailingSlash } from "@hono/hono/trailing-slash"; 4 | import { router as collection } from "./server/collection.ts"; 5 | import { router as geocloud_router } from "./server/geocloud.ts"; 6 | import { router as tianditu_router } from "./server/tianditu.ts"; 7 | import { router as jl1_router } from "./server/jilin1.ts"; 8 | import { router as bing } from "./server/tiles/bing.ts"; 9 | 10 | const app = new Hono(); 11 | 12 | app.get("/", (c) => { 13 | return c.html("On Deno Deploy 💖"); 14 | }); 15 | 16 | app.use("/dist/*", serveStatic({ root: "./" })); 17 | app.use(trimTrailingSlash()); 18 | 19 | app.route("/collection", collection); 20 | app.route("/geocloud", geocloud_router); 21 | app.route("/tianditu", tianditu_router); 22 | app.route("/jl1", jl1_router); 23 | app.route("/", bing); 24 | 25 | Deno.serve(app.fetch); 26 | -------------------------------------------------------------------------------- /src/maps.ts: -------------------------------------------------------------------------------- 1 | import { debug } from "./maps/debug.ts"; 2 | import { cap as google } from "./maps/google_map.ts"; 3 | import { cap as tianditu_js } from "./maps/tianditu/jiangsu.ts"; 4 | import { cap as xuzhou } from "./maps/tianditu/jiangsu_xuzhou.ts"; 5 | import { cap as osm } from "./maps/osm.ts"; 6 | import { cap as shipxy } from "./maps/shipxy.ts"; 7 | import { cap as amap } from "./maps/amap.ts"; 8 | import { cap as beijing } from "./maps/tianditu/beijing.ts"; 9 | import { cap as esri } from "./maps/esri.ts"; 10 | import { cap as gggis } from "./maps/gggis.ts"; 11 | import { cap as jl } from "./maps/tianditu/jilin.ts"; 12 | import { cap as yandex } from "./maps/yandex.ts"; 13 | import { collection } from "./maps/collection.ts"; 14 | 15 | export const maps = { 16 | debug, 17 | google, 18 | tianditu_js, 19 | xuzhou, 20 | osm, 21 | shipxy, 22 | amap, 23 | beijing, 24 | esri, 25 | gggis, 26 | jl, 27 | yandex, 28 | collection, 29 | }; 30 | -------------------------------------------------------------------------------- /src/maps/google_map.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Capabilities, 3 | MapLayer, 4 | mercator_bbox, 5 | Service, 6 | web_mercator_quad, 7 | } from "@liuxspro/capgen"; 8 | 9 | const satellite = new MapLayer( 10 | "Google Map - Satellite", 11 | "Google Map - Satellite", 12 | "gmap_sat", 13 | mercator_bbox, 14 | web_mercator_quad, 15 | "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}", 16 | "image/jpeg", 17 | ); 18 | 19 | const terrain_bg = new MapLayer( 20 | "Google Map - Terrain Background", 21 | "Google Map - Terrain Background", 22 | "gmap_terrain", 23 | mercator_bbox, 24 | web_mercator_quad, 25 | "http://mt0.google.com/vt/lyrs=p&x={x}&y={y}&z={z}&s=Ga&apistyle=s.e:l|p.v:off,s.t:1|s.e.g|p.v:off,s.t:3|s.e.g|p.v:off,s.t:2|s.e.g|p.v:off", 26 | "image/jpeg", 27 | ); 28 | 29 | export const layers = [satellite, terrain_bg]; 30 | 31 | export const service: Service = { 32 | title: "谷歌地图", 33 | abstract: "谷歌地图", 34 | keywords: ["谷歌地图"], 35 | }; 36 | 37 | export const cap = new Capabilities(service, layers).xml; 38 | -------------------------------------------------------------------------------- /src/maps/debug.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Capabilities, 3 | default_service, 4 | MapLayer, 5 | mercator_bbox, 6 | web_mercator_quad, 7 | world_crs84_quad, 8 | } from "@liuxspro/capgen"; 9 | 10 | const debug_layer = new MapLayer( 11 | "测试瓦片 EPSG:3857", 12 | "显示图块的行列号", 13 | "debug3857", 14 | mercator_bbox, 15 | web_mercator_quad.clone(), 16 | "https://liuxspro-service.deno.dev/tile/debug/{z}/{x}/{y}", 17 | "image/png", 18 | ); 19 | 20 | const debug_layer_4326 = new MapLayer( 21 | "测试瓦片 EPSG:4326", 22 | "显示图块的行列号", 23 | "debug4326", 24 | mercator_bbox, 25 | world_crs84_quad.clone(), 26 | "https://liuxspro-service.deno.dev/tile/debug/{z}/{x}/{y}", 27 | "image/png", 28 | ); 29 | 30 | const debug_layer_4326_quad = new MapLayer( 31 | "测试瓦片 EPSG:4326 Google Quadkey", 32 | "显示图块的 Quadkey", 33 | "debug4326quad", 34 | mercator_bbox, 35 | world_crs84_quad.clone(), 36 | "https://liuxspro-service.deno.dev/tile/debug/quad/{z}/{x}/{y}", 37 | "image/png", 38 | ); 39 | 40 | export const debug = new Capabilities( 41 | default_service, 42 | [debug_layer, debug_layer_4326, debug_layer_4326_quad], 43 | ).xml; 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 liuxspro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "specifiers": { 4 | "jsr:@es-toolkit/es-toolkit@^1.38.0": "1.38.0", 5 | "jsr:@hono/hono@^4.8.5": "4.8.5", 6 | "jsr:@liuxspro/capgen@~0.2.1": "0.2.1", 7 | "npm:minijinja-js@^2.9.0": "2.9.0" 8 | }, 9 | "jsr": { 10 | "@es-toolkit/es-toolkit@1.38.0": { 11 | "integrity": "7a3fa3bbe873116ec37ed68ef1df6b8ec3d89bac56edd354b7d12ff8cc0d5a0a" 12 | }, 13 | "@hono/hono@4.8.5": { 14 | "integrity": "78f72e532f378e379915a7e1ae7bd8a171b02324bd37b70877fd35375e8c2d6b" 15 | }, 16 | "@liuxspro/capgen@0.2.1": { 17 | "integrity": "d01bb326c682a73e3aaec0fb76259015d5d6e7783e2cb02673fafaf002deeda6", 18 | "dependencies": [ 19 | "jsr:@es-toolkit/es-toolkit", 20 | "npm:minijinja-js" 21 | ] 22 | } 23 | }, 24 | "npm": { 25 | "minijinja-js@2.9.0": { 26 | "integrity": "sha512-NDrwVXH5c+VfA41LrmINjlKUY+udSUAVSRjafyu0SiRkKJbczceR7DJK17UToZ59zc61dEroH78tesEYDLmegQ==" 27 | } 28 | }, 29 | "workspace": { 30 | "dependencies": [ 31 | "jsr:@hono/hono@^4.8.5", 32 | "jsr:@liuxspro/capgen@~0.2.1" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/maps/geoq.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * See https://thematic.geoq.cn/arcgis/rest/services 3 | */ 4 | 5 | import { 6 | Capabilities, 7 | MapLayer, 8 | mercator_bbox, 9 | Service, 10 | web_mercator_quad, 11 | } from "@liuxspro/capgen"; 12 | 13 | const geoq_gray = new MapLayer( 14 | "GeoQ - 灰色中国基础地图 (GCJ02)", 15 | "灰色中文不含兴趣点版中国基础地图 (GCJ02)", 16 | "geoq_gray", 17 | mercator_bbox, 18 | web_mercator_quad.clone(), 19 | "https://thematic.geoq.cn/arcgis/rest/services/ChinaOnlineStreetGray/MapServer/WMTS/tile/1.0.0/ChinaOnlineStreetGray/default/GoogleMapsCompatible/{z}/{y}/{x}.png", 20 | "image/png", 21 | ); 22 | 23 | const geoq_hydro = new MapLayer( 24 | "GeoQ - 水系图 (GCJ02)", 25 | "水系图 (GCJ02)", 26 | "geoq_hydro", 27 | mercator_bbox, 28 | web_mercator_quad.clone().setZoom(1, 13), 29 | "https://thematic.geoq.cn/arcgis/rest/services/ThematicMaps/WorldHydroMap/MapServer/WMTS/tile/1.0.0/ThematicMaps_WorldHydroMap/default/GoogleMapsCompatible/{z}/{y}/{x}.png", 30 | "image/png", 31 | ); 32 | 33 | export const layers = [geoq_gray, geoq_hydro]; 34 | 35 | export const service: Service = { 36 | title: "GeoQ", 37 | abstract: "GeoQ 底图", 38 | keywords: ["GeoQ"], 39 | }; 40 | 41 | export const cap = new Capabilities(service, layers).xml; 42 | -------------------------------------------------------------------------------- /src/maps/amap.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Capabilities, 3 | MapLayer, 4 | mercator_bbox, 5 | Service, 6 | web_mercator_quad, 7 | } from "@liuxspro/capgen"; 8 | 9 | const HOST = "https://wprd02.is.autonavi.com"; 10 | 11 | const satellite = new MapLayer( 12 | "高德 - 卫星影像 (GCJ02)", 13 | "高德 - 卫星影像 (GCJ02),有偏移", 14 | "amap_sat", 15 | mercator_bbox, 16 | web_mercator_quad, 17 | `${HOST}/appmaptile?lang=zh_cn&size=1&scale=1&style=6&x={x}&y={y}&z={z}`, 18 | "image/jpeg", 19 | ); 20 | 21 | const street = new MapLayer( 22 | "高德 - 矢量地图 (GCJ02)", 23 | "高德 - 矢量地图 (GCJ02),有偏移", 24 | "amap_str", 25 | mercator_bbox, 26 | web_mercator_quad, 27 | `${HOST}/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}`, 28 | "image/png", 29 | ); 30 | 31 | const annotes = new MapLayer( 32 | "高德 - 矢量注记 (GCJ02)", 33 | "高德 - 矢量注记 (GCJ02),有偏移", 34 | "amap_ann", 35 | mercator_bbox, 36 | web_mercator_quad, 37 | `${HOST}/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}`, 38 | "image/png", 39 | ); 40 | 41 | export const layers: MapLayer[] = [satellite, street, annotes]; 42 | 43 | export const service: Service = { 44 | title: "高德地图", 45 | abstract: "高德地图", 46 | keywords: ["高德地图"], 47 | }; 48 | 49 | export const cap = new Capabilities(service, layers).xml; 50 | -------------------------------------------------------------------------------- /src/maps/gggis.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Capabilities, 3 | default_service, 4 | MapLayer, 5 | mercator_bbox, 6 | web_mercator_quad, 7 | world_crs84_quad_less, 8 | } from "@liuxspro/capgen"; 9 | 10 | const satellite = new MapLayer( 11 | "Google Map - Satellite (谷谷地球)", 12 | "Google Map - Satellite", 13 | "gggis_sat", 14 | mercator_bbox, 15 | web_mercator_quad.clone(), 16 | "https://mt3v.gggis.com/maps/vt?lyrs=s&x={x}&y={y}&z={z}", 17 | "image/jpeg", 18 | ); 19 | 20 | // See https://siyouhua.gggis.com/api/image.html 21 | const _gggis_earth_new = new MapLayer( 22 | "Google Earth (谷谷地球 无水印高清)", 23 | "谷谷地球 无水印高清影像", 24 | "gggis_google_new", 25 | mercator_bbox, 26 | world_crs84_quad_less.clone(), 27 | "https://tileser.giiiis.com/new/{z}/{x}/{y}.jpg", 28 | "image/jpeg", 29 | ); 30 | 31 | // See https://siyouhua.gggis.com/api/zuixin.html 32 | const gggis_earth_timetile = new MapLayer( 33 | "Google Earth (谷谷地球 最新)", 34 | "谷谷地球 最新影像", 35 | "gggis_google_time", 36 | mercator_bbox, 37 | world_crs84_quad_less.clone(), 38 | "https://tileser.giiiis.com/timetile/0/{z}/{x}/{y}.jpg", 39 | "image/jpeg", 40 | ); 41 | 42 | export const gggis = [gggis_earth_timetile, satellite]; 43 | 44 | export const cap = new Capabilities( 45 | default_service, 46 | gggis, 47 | ).xml; 48 | -------------------------------------------------------------------------------- /src/maps/esri.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Capabilities, 3 | MapLayer, 4 | mercator_bbox, 5 | Service, 6 | web_mercator_quad, 7 | } from "@liuxspro/capgen"; 8 | 9 | const HOST = "https://server.arcgisonline.com/arcgis/rest/services"; 10 | const xyzpath = "MapServer/tile/{z}/{y}/{x}"; 11 | 12 | const maps = { 13 | "World Imagery": `${HOST}/World_Imagery/${xyzpath}`, 14 | "World Ocean Base": `${HOST}/Ocean/World_Ocean_Base/${xyzpath}`, 15 | "World Terrain Base": `${HOST}/World_Terrain_Base/${xyzpath}`, 16 | "World Physical Map": `${HOST}/World_Physical_Map/${xyzpath}`, 17 | "World Hillshade": `${HOST}/Elevation/World_Hillshade/${xyzpath}`, 18 | "World Light Gray": `${HOST}/Canvas/World_Light_Gray_Base/${xyzpath}`, 19 | }; 20 | 21 | export const esri_layers: MapLayer[] = []; 22 | 23 | Object.entries(maps).forEach(([key, value]) => { 24 | esri_layers.push( 25 | new MapLayer( 26 | `ERSI ${key}`, 27 | `ERSI ${key}`, 28 | `esri_${key.replaceAll(" ", "")}`, 29 | mercator_bbox, 30 | web_mercator_quad.clone(), 31 | value, 32 | "image/jpeg", 33 | ), 34 | ); 35 | }); 36 | 37 | export const service: Service = { 38 | title: "ESRI Maps", 39 | abstract: "ESRI Maps", 40 | keywords: ["ESRI"], 41 | }; 42 | 43 | export const cap = new Capabilities(service, esri_layers).xml; 44 | -------------------------------------------------------------------------------- /src/maps/collection.ts: -------------------------------------------------------------------------------- 1 | import { Capabilities, Service } from "@liuxspro/capgen"; 2 | 3 | import { layers as osm } from "../maps/osm.ts"; 4 | import { layers as google } from "../maps/google_map.ts"; 5 | import { layers as amap } from "../maps/amap.ts"; 6 | import { layers as haitu } from "../maps/shipxy.ts"; 7 | import { layers as bing } from "../maps/bing.ts"; 8 | import { tianditu_layers } from "../maps/tianditu.ts"; 9 | import { esri_layers } from "../maps/esri.ts"; 10 | import { gggis } from "../maps/gggis.ts"; 11 | import { layers as yandex } from "../maps/yandex.ts"; 12 | import { layers as geoq } from "../maps/geoq.ts"; 13 | 14 | const tianditu = tianditu_layers.filter((layer) => layer.id.includes("_w")).map( 15 | (layer) => { 16 | layer.set_token("tk", "4267820f43926eaf808d61dc07269beb"); // Key from Geoscene 17 | return layer; 18 | }, 19 | ); 20 | 21 | export const service: Service = { 22 | title: "Collection of Basemaps", 23 | abstract: 24 | "本服务提供常用 XYZ 格式地图瓦片合集,通过标准 WMTS 接口发布,支持各类 GIS 平台调用。by liuxspro@gmail.com", 25 | keywords: ["XYZ", "basemaps"], 26 | }; 27 | 28 | export const collection = new Capabilities(service, [ 29 | ...osm, 30 | ...google, 31 | ...amap, 32 | ...haitu, 33 | ...bing, 34 | ...tianditu, 35 | ...esri_layers, 36 | ...gggis, 37 | ...yandex, 38 | ...geoq, 39 | ]).xml; 40 | -------------------------------------------------------------------------------- /src/server/tianditu.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "@hono/hono"; 2 | import { tianditu_cap } from "../maps/tianditu.ts"; 3 | import { gen_sd_cap } from "../maps/tianditu_sd.ts"; 4 | import { cap as fujian } from "../maps/tianditu/fujian.ts"; 5 | 6 | export const router = new Hono(); 7 | 8 | router.get("/", (c) => { 9 | const tk_name = "tdt"; // 如果用tk, arcgis 设置的自定义参数会导致瓦片URL tk 重复 10 | const tdt_tk = c.req.header(tk_name) || c.req.query(tk_name); 11 | if (tdt_tk) { 12 | // 创建图层副本并设置token 13 | let token = tdt_tk; 14 | // 处理请求参数后携带 `/1.0.0/WMTSCapabilities.xml`的情况(arcgis 10.2) 15 | if (tdt_tk.includes("/")) { 16 | token = tdt_tk.split("/")[0]; 17 | } 18 | // 处理请求参数后携带 `?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetCapabilities`的情况(arcgis 10.2) 19 | if (tdt_tk.includes("?")) { 20 | token = tdt_tk.split("?")[0]; 21 | } 22 | c.header("Content-Type", "text/xml;charset=UTF-8"); 23 | return c.body(tianditu_cap(token)); 24 | } else { 25 | return c.text("Tianditu token is required", 400); 26 | } 27 | }); 28 | 29 | router.get("/sdhis/:id/:el", (c) => { 30 | const { id, el } = c.req.param(); 31 | const z = parseInt(el); 32 | const tk = c.req.query("tk") || ""; 33 | c.header("Content-Type", "text/xml;charset=UTF-8"); 34 | return c.body(gen_sd_cap(id, 3, z, tk)); 35 | }); 36 | 37 | router.get("/fujian", (c) => { 38 | c.header("Content-Type", "text/xml;charset=UTF-8"); 39 | return c.body(fujian); 40 | }); 41 | -------------------------------------------------------------------------------- /src/maps/tianditu_sd.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 山东天地图历史影像 3 | * 根据 影像元数据 接口查询历史影像列表 4 | * https://www.sdmap.gov.cn/resourceCenter.html#/funInfo?sign=info&type=imagemetaData 5 | * 瓦片模板 https://service.sdmap.gov.cn/hisimage/weipianjn202503?tk=&layer=c&style=c&tilematrixset=c&Service=WMTS&Request=GetTile&TileMatrix=18&TileCol=216343&TileRow=38819 6 | */ 7 | 8 | import { 9 | Capabilities, 10 | cgcs2000_quad, 11 | GeoPoint, 12 | MapLayer, 13 | Service, 14 | } from "@liuxspro/capgen"; 15 | 16 | const host = "https://service.sdmap.gov.cn/hisimage"; 17 | const sd_bbox: [GeoPoint, GeoPoint] = [ 18 | { lon: 114.2298, lat: 33.9389 }, // 西南角 (LowerCorner) 19 | { lon: 123.4005, lat: 38.9048 }, // 东北角 (UpperCorner) 20 | ]; 21 | const service: Service = { 22 | title: "山东天地图历史影像", 23 | abstract: "山东天地图历史影像", 24 | keywords: ["山东天地图"], 25 | }; 26 | 27 | /** 28 | * 根据id和层级信息生成能力文档 29 | * 供 QGIS 插件调用 30 | * @param id 地图 id 31 | * @param sl 起始层级 32 | * @param el 终止层级 33 | * @param tk token 34 | * @returns 能力文档 35 | */ 36 | export function gen_sd_cap( 37 | id: string, 38 | sl: number, 39 | el: number, 40 | tk: string, 41 | ): string { 42 | const tile_url = 43 | `${host}/${id}?tk=${tk}&layer=c&style=c&tilematrixset=c&Service=WMTS&Request=GetTile&TileMatrix={z}&TileCol={x}&TileRow={y}`; 44 | const layer = new MapLayer( 45 | id, 46 | id, 47 | id, 48 | sd_bbox, 49 | cgcs2000_quad.clone().setZoom(sl, el), 50 | tile_url, 51 | "image/jpeg", 52 | ); 53 | 54 | return new Capabilities(service, [layer]).xml; 55 | } 56 | -------------------------------------------------------------------------------- /src/maps/jilin1.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Capabilities, 3 | MapLayer, 4 | mercator_bbox, 5 | Service, 6 | web_mercator_quad, 7 | } from "@liuxspro/capgen"; 8 | 9 | // https://www.jl1mall.com/rskit/RSsserviceManage 10 | 11 | const jl1_2022 = new MapLayer( 12 | "吉林一号 - 2022年度全国高质量一张图", 13 | "吉林一号 - 2022年度全国高质量一张图", 14 | "jl1_2022", 15 | mercator_bbox, 16 | web_mercator_quad.clone(), 17 | "https://api.jl1mall.com/getMap?TileMatrix={z}&TileCol={x}&TileRow={y}&sch=wmts&route=1&mk=bd60ffe96379e0c9cbc1be02b06e3622", 18 | "image/jpeg", 19 | ); 20 | 21 | const jl1_2023 = new MapLayer( 22 | "吉林一号 - 2023年度全国高质量一张图", 23 | "吉林一号 - 2023年度全国高质量一张图", 24 | "jl1_2023", 25 | mercator_bbox, 26 | web_mercator_quad.clone(), 27 | "https://api.jl1mall.com/getMap?TileMatrix={z}&TileCol={x}&TileRow={y}&sch=wmts&route=1&mk=73ad26c4aa6957eef051ecc5a15308b4", 28 | "image/jpeg", 29 | ); 30 | 31 | const jl1_2024 = new MapLayer( 32 | "吉林一号 - 2024年度全国高质量一张图", 33 | "吉林一号 - 2024年度全国高质量一张图", 34 | "jl1_2024", 35 | mercator_bbox, 36 | web_mercator_quad.clone(), 37 | "https://api.jl1mall.com/getMap?TileMatrix={z}&TileCol={x}&TileRow={y}&sch=wmts&route=1&mk=3ddec00f5f435270285ffc7ad1a60ce5", 38 | "image/jpeg", 39 | ); 40 | 41 | export const layers = [jl1_2022, jl1_2023, jl1_2024]; 42 | 43 | export const service: Service = { 44 | title: "吉林一号", 45 | abstract: "吉林一号", 46 | keywords: ["吉林一号"], 47 | }; 48 | 49 | export function jl1_cap(token: string) { 50 | return new Capabilities( 51 | service, 52 | layers.map((layer) => { 53 | layer.set_token("tk", token); 54 | return layer; 55 | }), 56 | ).xml; 57 | } 58 | 59 | // export const cap = new Capabilities(service, layers).xml; 60 | -------------------------------------------------------------------------------- /src/maps/tianditu/jiangsu_xuzhou.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 天地图徐州 影像对比系统 3 | * http://www.xzmap.gov.cn/xuzhou/compereXZ/map/comp.jsp 4 | * 测试接口 5 | * http://221.229.211.117/DOM/service-tile.jsp 6 | * 7 | * 瓦片模板 8 | * http://221.229.211.117/DOM/wmts/BZ_DOM_24_QS/BZ_DOM_24_QS/BZ_DOM_24_QS_Matrix_0/18/40587/216393.png 9 | * http://221.229.211.117/DOM/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=BZ_DOM_24_QS&STYLE=BZ_DOM_24_QS&TILEMATRIXSET=BZ_DOM_24_QS_Matrix_0&TILEMATRIX=18&TILEROW=40587&TILECOL=216393 10 | */ 11 | 12 | import { 13 | Capabilities, 14 | GeoPoint, 15 | MapLayer, 16 | Service, 17 | world_crs84_quad, 18 | } from "@liuxspro/capgen"; 19 | 20 | const xz_maps = { 21 | BZ_DOM_24_QS: "徐州 2024 年影像", 22 | BZ_DOM_23_QS: "徐州 2023 年影像", 23 | BZ_DOM_22_QS: "徐州 2022 年影像", 24 | BZ_DOM_21_QS: "徐州 2021 年影像", 25 | BZ_DOM_20_QS: "徐州 2020 年影像", 26 | BZ_DOM_19_QS: "徐州 2019 年影像", 27 | BZ_DOM_18_QS: "徐州 2018 年影像", 28 | }; 29 | 30 | const xz_bbox: [GeoPoint, GeoPoint] = [ 31 | { lon: 116.015625, lat: 33.398438 }, // 西南角 (LowerCorner) 32 | { lon: 118.828125, lat: 35.15625 }, // 东北角 (UpperCorner) 33 | ]; 34 | 35 | const xz_layers: MapLayer[] = []; 36 | 37 | Object.entries(xz_maps).forEach(([key, name]) => { 38 | xz_layers.push( 39 | new MapLayer( 40 | name, 41 | name, 42 | key, 43 | xz_bbox, 44 | world_crs84_quad.clone().setZoom(11, 18), 45 | `http://221.229.211.117/DOM/wmts/${key}/${key}/${key}_Matrix_0/{z}/{y}/{x}.png`, 46 | "image/jpeg", 47 | ), 48 | ); 49 | }); 50 | const service: Service = { 51 | title: "天地图 徐州", 52 | abstract: "天地图 徐州 历史影像", 53 | keywords: ["天地图", "徐州", "历史影像"], 54 | }; 55 | export const cap = new Capabilities(service, xz_layers).xml; 56 | -------------------------------------------------------------------------------- /src/maps/tianditu.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Capabilities, 3 | cgcs2000_quad, 4 | MapLayer, 5 | mercator_bbox, 6 | Service, 7 | web_mercator_quad, 8 | } from "@liuxspro/capgen"; 9 | 10 | const tianditu_w = { 11 | vec_w: "天地图 - 矢量地图(EPSG:3857)", 12 | cva_w: "天地图 - 矢量注记(EPSG:3857)", 13 | img_w: "天地图 - 影像地图(EPSG:3857)", 14 | cia_w: "天地图 - 影像注记(EPSG:3857)", 15 | ter_w: "天地图 - 地形晕染(EPSG:3857)", 16 | cta_w: "天地图 - 地形注记(EPSG:3857)", 17 | ibo_w: "天地图 - 全球境界(EPSG:3857)", 18 | vec_c: "天地图 - 矢量地图(EPSG:4490)", 19 | cva_c: "天地图 - 矢量注记(EPSG:4490)", 20 | img_c: "天地图 - 影像地图(EPSG:4490)", 21 | cia_c: "天地图 - 影像注记(EPSG:4490)", 22 | ter_c: "天地图 - 地形晕染(EPSG:4490)", 23 | cta_c: "天地图 - 地形注记(EPSG:4490)", 24 | ibo_c: "天地图 - 全球境界(EPSG:4490)", 25 | }; 26 | 27 | export const img_format = { 28 | vec_c: "image/png", 29 | cva_c: "image/png", 30 | img_c: "image/jpeg", 31 | cia_c: "image/png", 32 | ter_c: "image/jpeg", 33 | cta_c: "image/png", 34 | ibo_c: "image/png", 35 | vec_w: "image/png", 36 | cva_w: "image/png", 37 | img_w: "image/jpeg", 38 | cia_w: "image/png", 39 | ter_w: "image/jpeg", 40 | cta_w: "image/png", 41 | ibo_w: "image/png", 42 | }; 43 | 44 | export const service: Service = { 45 | title: "天地图服务", 46 | abstract: "天地图服务", 47 | keywords: ["天地图"], 48 | }; 49 | export const tianditu_layers: MapLayer[] = []; 50 | 51 | Object.entries(tianditu_w).forEach(([key, value]) => { 52 | const format = img_format[key as keyof typeof tianditu_w]; 53 | let matrix = web_mercator_quad; 54 | if (key.includes("_c")) { 55 | matrix = cgcs2000_quad; 56 | } 57 | tianditu_layers.push( 58 | new MapLayer( 59 | value, 60 | value, 61 | `tianditu_${key}`, 62 | mercator_bbox, 63 | matrix, 64 | `https://t6.tianditu.gov.cn/DataServer?T=${key}&x={x}&y={y}&l={z}`, 65 | format, 66 | ), 67 | ); 68 | }); 69 | 70 | export const cap = new Capabilities(service, tianditu_layers).xml; 71 | 72 | export function tianditu_cap(token: string) { 73 | return new Capabilities( 74 | service, 75 | tianditu_layers.map((layer) => { 76 | layer.set_token("tk", token); 77 | return layer; 78 | }), 79 | ).xml; 80 | } 81 | -------------------------------------------------------------------------------- /src/maps/tianditu/fujian.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 天地图福建 多时相 3 | * https://fujian.tianditu.gov.cn/map/?multiImage=true 4 | * 5 | * 坐标系: EPSG:4490 6 | * 瓦片模板 7 | * https://s0.fjmap.net/img_fj_2025_his/wmts?TIME=2025-11-30%200%3A00%3A00&SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&layer=img_his&style=img_his&format=image%2Ftile&tilematrixset=Matrix_0 8 | */ 9 | 10 | import { 11 | Capabilities, 12 | cgcs2000_quad, 13 | GeoPoint, 14 | MapLayer, 15 | Service, 16 | } from "@liuxspro/capgen"; 17 | 18 | const fj_bbox: [GeoPoint, GeoPoint] = [ 19 | { lon: 115.4958786070348, lat: 23.013545965473952 }, 20 | { lon: 121.47440903584844, lat: 29.032646732660623 }, 21 | ]; 22 | 23 | const host = "https://s0.fjmap.net"; 24 | const zyx = 25 | "&SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&layer=img_his&style=img_his&format=image%2Ftile&tilematrixset=Matrix_0"; 26 | 27 | const img_fj_2025_his = { 28 | "2025-11-30%200%3A00%3A00": "福建 多时相影像 2025-11-30", 29 | "2025-10-31%200%3A00%3A00": "福建 多时相影像 2025-10-31", 30 | "2025-9-30%200%3A00%3A00": "福建 多时相影像 2025-9-30", 31 | "2025-8-31%200%3A00%3A00": "福建 多时相影像 2025-8-31", 32 | "2025-7-31%200%3A00%3A00": "福建 多时相影像 2025-7-31", 33 | "2025-6-30%200%3A00%3A00": "福建 多时相影像 2025-6-30", 34 | "2025-5-31%200%3A00%3A00": "福建 多时相影像 2025-5-31", 35 | "2025-4-30%200%3A00%3A00": "福建 多时相影像 2025-4-30", 36 | "2025-3-31%200%3A00%3A00": "福建 多时相影像 2025-3-31", 37 | "2025-2-28%200%3A00%3A00": "福建 多时相影像 2025-2-28", 38 | "2025-1-31%200%3A00%3A00": "福建 多时相影像 2025-1-31", 39 | }; 40 | 41 | export const service: Service = { 42 | title: "天地图 福建", 43 | abstract: "天地图 福建 多时相影像", 44 | keywords: ["天地图", "福建", "多时相影像"], 45 | }; 46 | 47 | const tianditu_fj_layers: MapLayer[] = []; 48 | Object.entries(img_fj_2025_his).forEach(([time, name]) => { 49 | tianditu_fj_layers.push( 50 | new MapLayer( 51 | name, 52 | name, 53 | `tianditu_fj_2025_${ 54 | time.slice(0, 10).replaceAll("-", "_").replaceAll("%", "") 55 | }`, 56 | fj_bbox, 57 | cgcs2000_quad.clone().setZoom(7, 18), 58 | `${host}/img_fj_2025_his/wmts?TIME=${time}${zyx}`, 59 | ), 60 | ); 61 | }); 62 | 63 | const ter_layer = new MapLayer( 64 | "天地图 福建 地形图 2021", 65 | "天地图 福建 地形图 2021", 66 | "tianditu_fj_ter", 67 | fj_bbox, 68 | cgcs2000_quad.clone().setZoom(7, 18), 69 | `https://s0.fjmap.net/ter_fj_2021/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=TER_FJ&STYLE=default&FORMAT=tiles&TILEMATRIXSET=default028mm&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}`, 70 | ); 71 | 72 | tianditu_fj_layers.push(ter_layer); 73 | 74 | export const cap = new Capabilities(service, tianditu_fj_layers).xml; 75 | -------------------------------------------------------------------------------- /src/maps/geocloud.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 能力文档地址 3 | * https://igss.cgs.gov.cn:6160/igs/rest/ogc/qg50w_20210416_F7qGy9A7/WMTSServer/1.0.0/WMTSCapabilities.xml?tk=您的token 4 | * 瓦片URL 5 | * KVP 方式 6 | * https://igss.cgs.gov.cn:6160/igs/rest/ogc/{LayerId}/WMTSServer? 7 | * tk={tk}&Width=256&Height=256&layer={LayerId}&style=default&tilematrixset=EPSG%3A4326_{LayerId}_028mm_GB 8 | * &Service=WMTS&Request=GetTile&Version=1.0.0&Format=image%2Fpng&TileMatrix={z}&TileCol={x}&TileRow={y} 9 | * REST 方式 10 | * https://igss.cgs.gov.cn:6160/igs/rest/ogc/{LayerId}/WMTSServer/1.0.0/{LayerId}/default/EPSG%3A4326_{LayerId}_028mm_GB/{z}/{y}/{x}.png?tk={tk} 11 | * 12 | * 注意申请 tk 的类型要为“客户端” 13 | * QGIS 默认的 User-Agent 为`Mozilla/5.0 QGIS/34200/Windows 11 Version 2009` 14 | * 既不是浏览器端(还需要包含Chrome),也不是服务端(不能包含Mozilla) 15 | * 16 | * 注意地质云的瓦片矩阵集的z比正常少1 {z-1} 17 | */ 18 | 19 | import { 20 | Capabilities, 21 | GeoPoint, 22 | MapLayer, 23 | Service, 24 | world_crs84_quad_less, 25 | } from "@liuxspro/capgen"; 26 | 27 | const china_bbox: [GeoPoint, GeoPoint] = [ 28 | { lon: 73.49895477, lat: 3.83254099 }, // 西南角 (LowerCorner) 29 | { lon: 135.08738708, lat: 53.55849838 }, // 东北角 (UpperCorner) 30 | ]; 31 | 32 | export const service: Service = { 33 | title: "地质云 GeoCloud", 34 | abstract: "地质云 GeoCloud 服务 By liuxspro@gmail.com", 35 | keywords: ["地质云", "GeoCloud"], 36 | }; 37 | 38 | const layers = { 39 | qg250w_20210416_ZAZSeOGX: "全国 1:250 万地质图", // 最大 16 级(17) 40 | qg150w_20210416_BIwqE0wU: "全国 1:150 万地质图", // 最大 14 级(15) 41 | 全国100万地质图_20210330_rpam5kdJ: "全国 1:100 万地质图", // 最大 11 级(12) 42 | qg50w_20210416_F7qGy9A7: "全国 1:50 万地质图", // 最大 13 级(14) 43 | qg20_20210401_FCnDDRJd: "全国 1:20 万地质图", // 最大 14 级(15) 44 | }; 45 | 46 | export const geocloud_layers: MapLayer[] = []; 47 | 48 | Object.entries(layers).forEach(([key, value]) => { 49 | const HOST = "https://igss.cgs.gov.cn:6160"; 50 | // 100 万地质图的中文名称会导致在 arcmap 中无法加载瓦片 51 | // 进行 URL 编码后方可以正常加载 52 | // id 仍然为未编码的名称 否则 QGIS 无法加载 53 | const ekey = encodeURIComponent(key); 54 | const url = 55 | `${HOST}/igs/rest/ogc/${ekey}/WMTSServer?Width=256&Height=256&layer=${ekey}&style=default&tilematrixset=EPSG%3A4326_${ekey}_028mm_GB&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image%2Fpng&TileMatrix={z}&TileCol={x}&TileRow={y}`; 56 | geocloud_layers.push( 57 | new MapLayer( 58 | value, 59 | value, 60 | key, 61 | china_bbox, 62 | world_crs84_quad_less.clone().setZoom(2, 15), 63 | url, 64 | "image/png", 65 | ), 66 | ); 67 | }); 68 | 69 | export const cap = new Capabilities(service, geocloud_layers); 70 | 71 | export function geocloud_cap(token: string) { 72 | return new Capabilities( 73 | service, 74 | geocloud_layers.map((layer) => { 75 | layer.set_token("tk", token); 76 | return layer; 77 | }), 78 | ).xml; 79 | } 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # vitepress build output 108 | **/.vitepress/dist 109 | 110 | # vitepress cache directory 111 | **/.vitepress/cache 112 | 113 | # Docusaurus cache and generated files 114 | .docusaurus 115 | 116 | # Serverless directories 117 | .serverless/ 118 | 119 | # FuseBox cache 120 | .fusebox/ 121 | 122 | # DynamoDB Local files 123 | .dynamodb/ 124 | 125 | # TernJS port file 126 | .tern-port 127 | 128 | # Stores VSCode versions used for testing VSCode extensions 129 | .vscode-test 130 | 131 | # yarn v2 132 | .yarn/cache 133 | .yarn/unplugged 134 | .yarn/build-state.yml 135 | .yarn/install-state.gz 136 | .pnp.* 137 | -------------------------------------------------------------------------------- /src/maps/tianditu/beijing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 天地图 北京 3 | * 多时相影像 https://beijing.tianditu.gov.cn/bjtdt-main/compare.html 4 | * 瓦片 URL https://beijing.tianditu.gov.cn/iserver/services/map-2022_img/rest/maps/tdt_img_202401/zxyTileImage/{z}/{x}/{y}.png?width=256&height=256&transparent=true 5 | * See: https://iportal.supermap.io/iportal/help/html/zh/mergedProjects/SuperMapiServerRESTAPI/root/maps/map/zxyTileImage/zxyTileImage.htm 6 | */ 7 | 8 | import { 9 | Capabilities, 10 | GeoPoint, 11 | MapLayer, 12 | Service, 13 | web_mercator_quad, 14 | } from "@liuxspro/capgen"; 15 | 16 | const beijing_maps = { 17 | tdt_img_202203: "北京 - 2022年03月影像", 18 | tdt_img_202206: "北京 - 2022年06月影像", 19 | tdt_img_202209: "北京 - 2022年09月影像", 20 | tdt_img_202212: "北京 - 2022年12月影像", 21 | tdt_img_202301: "北京 - 2023年01月影像", 22 | tdt_img_202302: "北京 - 2023年02月影像", 23 | tdt_img_202303: "北京 - 2023年03月影像", 24 | tdt_img_202304: "北京 - 2023年04月影像", 25 | tdt_img_202305: "北京 - 2023年05月影像", 26 | tdt_img_202306: "北京 - 2023年06月影像", 27 | tdt_img_202401: "北京 - 2024年01月影像", 28 | tdt_img_202403: "北京 - 2024年03月影像", 29 | tdt_img_202404: "北京 - 2024年04月影像", 30 | tdt_img_202405: "北京 - 2024年05月影像", 31 | tdt_img_202406: "北京 - 2024年06月影像", 32 | }; 33 | 34 | const beijing_old_maps = { 35 | tdt_img_1951: "北京 - 1951年影像", 36 | tdt_img_1966: "北京 - 1966年影像", 37 | tdt_img_1996: "北京 - 1996年影像", 38 | }; 39 | 40 | const beijing_bbox: [GeoPoint, GeoPoint] = [ 41 | { lon: 115.413811, lat: 39.443493 }, // 西南角 (LowerCorner) 42 | { lon: 117.506111, lat: 41.058609 }, // 东北角 (UpperCorner) 43 | ]; 44 | 45 | // 1951、1966、1996年影像范围 46 | const beijing_old_bbox: [GeoPoint, GeoPoint] = [ 47 | { lon: 116.28899792, lat: 39.82830693 }, 48 | { lon: 116.47452581, lat: 39.98153392 }, 49 | ]; 50 | 51 | const beijing_layers: MapLayer[] = []; 52 | const beijing_old_layers: MapLayer[] = []; 53 | 54 | Object.entries(beijing_maps).forEach(([key, name]) => { 55 | beijing_layers.push( 56 | new MapLayer( 57 | name, 58 | name, 59 | key, 60 | beijing_bbox, 61 | web_mercator_quad.clone(), 62 | `https://beijing.tianditu.gov.cn/iserver/services/map-2022_img/rest/maps/${key}/zxyTileImage/{z}/{x}/{y}.png?width=256&height=256&transparent=true`, 63 | "image/png", 64 | ), 65 | ); 66 | }); 67 | 68 | Object.entries(beijing_old_maps).forEach(([key, name]) => { 69 | beijing_old_layers.push( 70 | new MapLayer( 71 | name, 72 | name, 73 | key, 74 | beijing_old_bbox, 75 | web_mercator_quad.clone(), 76 | `https://beijing.tianditu.gov.cn/iserver/services/map-2022_img/rest/maps/${key}/zxyTileImage/{z}/{x}/{y}.png?width=256&height=256&transparent=true`, 77 | "image/png", 78 | ), 79 | ); 80 | }); 81 | 82 | export const service: Service = { 83 | title: "天地图 北京", 84 | abstract: "天地图 北京 多时相影像", 85 | keywords: ["天地图", "北京", "多时相影像"], 86 | }; 87 | export const cap = new Capabilities(service, [ 88 | ...beijing_old_layers, 89 | ...beijing_layers, 90 | ]).xml; 91 | -------------------------------------------------------------------------------- /src/maps/tianditu/jiangsu.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 天地图江苏 多时相 3 | * https://jiangsu.tianditu.gov.cn/mulitdate/index.html 4 | * https://jiangsu.tianditu.gov.cn/server/mulitdate/getConfig?type=%E5%A4%9A%E6%97%B6%E7%9B%B8%E9%85%8D%E7%BD%AE 5 | * 6 | * 坐标系: EPSG:4490 7 | * 瓦片模板 8 | * https://jiangsu.tianditu.gov.cn/historyraster/rest/services/History/yxdt_js_1966_2k/MapServer/tile/{z}/{y}/{x} 9 | * https://jiangsu.tianditu.gov.cn/tdtsite05/rest/services/tdtjs/js_img2024_r05/MapServer/tile/12/658/3397 10 | */ 11 | 12 | import { 13 | Capabilities, 14 | cgcs2000_quad, 15 | GeoPoint, 16 | MapLayer, 17 | Service, 18 | } from "@liuxspro/capgen"; 19 | 20 | const host = "https://jiangsu.tianditu.gov.cn"; 21 | const zyx = "MapServer/tile/{z}/{y}/{x}"; 22 | 23 | const js_bbox: [GeoPoint, GeoPoint] = [ 24 | { lon: 116.10358, lat: 30.710719 }, // 西南角 (LowerCorner) 25 | { lon: 122.090304, lat: 35.212659 }, // 东北角 (UpperCorner) 26 | ]; 27 | 28 | const map_name = { 29 | js_yxdt_1966: "江苏 1966年影像地图", 30 | js_yxdt_1976: "江苏 1976年影像地图", 31 | js_yxdt_2005: "江苏 2005年影像地图", 32 | js_yxdt_2010: "江苏 2010年影像地图", 33 | js_yxdt_2012: "江苏 2012年影像地图", 34 | js_yxdt_2014: "江苏 2014年影像地图", 35 | js_yxdt_2016: "江苏 2016年影像地图", 36 | js_yxdt_2017: "江苏 2017年影像地图", 37 | js_yxdt_2018: "江苏 2018年影像地图", 38 | js_yxdt_2019: "江苏 2019年影像地图", 39 | js_yxdt_2020: "江苏 2020年影像地图", 40 | js_yxdt_2021: "江苏 2021年影像地图", 41 | js_yxdt_2022: "江苏 2022年影像地图", 42 | js_yxdt_2023: "江苏 2023年影像地图", 43 | js_yxdt_2024: "江苏 2024年影像地图", 44 | }; 45 | 46 | const map_url = { 47 | js_yxdt_1966: 48 | `${host}/historyraster/rest/services/History/yxdt_js_1966_2k/${zyx}`, 49 | js_yxdt_1976: 50 | `${host}/historyraster/rest/services/History/yxdt_js_1976_2k/${zyx}`, 51 | js_yxdt_2005: 52 | `${host}/historyraster/rest/services/History/js_yxdt_2005/${zyx}`, 53 | js_yxdt_2010: 54 | `${host}/historyraster/rest/services/History/js_yxdt_2010/${zyx}`, 55 | js_yxdt_2012: 56 | `${host}/historyraster/rest/services/History/js_yxdt_2012/${zyx}`, 57 | js_yxdt_2014: 58 | `${host}/historyraster/rest/services/History/js_yxdt_2014/${zyx}`, 59 | js_yxdt_2016: `${host}/mapjs2/rest/services/MapJS/js_yxdt_2016/${zyx}`, 60 | js_yxdt_2017: 61 | `${host}/historyraster/rest/services/History/js_yxdt_2017/${zyx}`, 62 | js_yxdt_2018: `${host}/mapjs2/rest/services/MapJS/js_yxdt_2018/${zyx}`, 63 | js_yxdt_2019: 64 | `${host}/historyraster/rest/services/History/js_yxdt_2019/${zyx}`, 65 | js_yxdt_2020: 66 | `${host}/historyraster/rest/services/History/js_yxdt_2020/${zyx}`, 67 | js_yxdt_2021: 68 | `${host}/historyraster/rest/services/History/js_yxdt_2021/${zyx}`, 69 | js_yxdt_2022: `${host}/tdtsite05/rest/services/tdtjs/js_img2022_r05/${zyx}`, 70 | js_yxdt_2023: `${host}/tdtsite05/rest/services/tdtjs/js_img2023_r05/${zyx}`, 71 | js_yxdt_2024: `${host}/tdtsite05/rest/services/tdtjs/js_img2024_r05/${zyx}`, 72 | }; 73 | 74 | const tianditu_js_layers: MapLayer[] = []; 75 | 76 | Object.entries(map_url).forEach(([key, url]) => { 77 | tianditu_js_layers.push( 78 | new MapLayer( 79 | map_name[key as keyof typeof map_name], 80 | map_name[key as keyof typeof map_name], 81 | key, 82 | js_bbox, 83 | cgcs2000_quad, 84 | url, 85 | "image/jpeg", 86 | ), 87 | ); 88 | }); 89 | export const service: Service = { 90 | title: "天地图 江苏", 91 | abstract: "天地图 江苏 历史影像", 92 | keywords: ["天地图", "江苏", "历史影像"], 93 | }; 94 | 95 | export const cap = new Capabilities(service, tianditu_js_layers).xml; 96 | -------------------------------------------------------------------------------- /src/maps/osm.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * See https://wiki.openstreetmap.org/wiki/Raster_tile_providers 3 | */ 4 | 5 | import { 6 | Capabilities, 7 | MapLayer, 8 | mercator_bbox, 9 | Service, 10 | web_mercator_quad, 11 | web_mercator_quad_hd, 12 | } from "@liuxspro/capgen"; 13 | 14 | // OpenStreetMap's Standard tile layer 15 | const osm = new MapLayer( 16 | "OpenStreetMap Standard", 17 | "OpenStreetMap's Standard tile layer", 18 | "osm_std", 19 | mercator_bbox, 20 | web_mercator_quad.clone(), 21 | "https://tile.openstreetmap.org/{z}/{x}/{y}.png", 22 | "image/png", 23 | ); 24 | 25 | // German fork of the Standard tile layer 26 | const osm_de = new MapLayer( 27 | "OpenStreetMap Standard (German fork)", 28 | "German fork of the Standard tile layer", 29 | "osm_de", 30 | mercator_bbox, 31 | web_mercator_quad.clone(), 32 | "https://tile.openstreetmap.de/{z}/{x}/{y}.png", 33 | "image/png", 34 | ); 35 | 36 | // from OsmAnd 37 | // https://osmand.net/docs/user/map/raster-maps/ 38 | const osmand = new MapLayer( 39 | "OpenStreetMap Standard HD (OsmAnd 512px)", 40 | "OsmAnd tile layer", 41 | "osm_and", 42 | mercator_bbox, 43 | web_mercator_quad_hd.clone(), 44 | "https://tile.osmand.net/hd/{z}/{x}/{y}.png", 45 | "image/png", 46 | ); 47 | 48 | // from F4map 49 | // https://www.f4map.com 50 | const f4map_2d = new MapLayer( 51 | "F4map - 2D", 52 | "F4map - 2D", 53 | "osm_f4map_2d", 54 | mercator_bbox, 55 | web_mercator_quad_hd.clone(), 56 | "https://tile.f4map.com/tiles/f4_2d/{z}/{x}/{y}.png", 57 | "image/png", 58 | ); 59 | 60 | // from Carto 61 | // https://basemaps.cartocdn.com/ 62 | const carto_voyager = new MapLayer( 63 | "Carto - Voyager (nolabels)", 64 | "Carto - Voyager (nolabels)", 65 | "carto_voyager", 66 | mercator_bbox, 67 | web_mercator_quad_hd.clone(), 68 | "https://basemaps.cartocdn.com/rastertiles/voyager_no_labels_no_buildings/{z}/{x}/{y}@2x.png", 69 | "image/png", 70 | ); 71 | 72 | const carto_light = new MapLayer( 73 | "Carto - Light (nolabels)", 74 | "Carto - Light (nolabels)", 75 | "carto_light", 76 | mercator_bbox, 77 | web_mercator_quad_hd.clone(), 78 | "https://basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}@2x.png", 79 | "image/png", 80 | ); 81 | 82 | const carto_dark = new MapLayer( 83 | "Carto - Dark (nolabels)", 84 | "Carto - Dark (nolabels)", 85 | "carto_dark", 86 | mercator_bbox, 87 | web_mercator_quad_hd.clone(), 88 | "https://basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}@2x.png", 89 | "image/png", 90 | ); 91 | 92 | const carto = [carto_voyager, carto_light, carto_dark]; 93 | 94 | const windy_dark = new MapLayer( 95 | "Windy - Darkmap", 96 | "Windy - Darkmap", 97 | "windy_dark", 98 | mercator_bbox, 99 | web_mercator_quad_hd.clone().setZoom(1, 11), 100 | "https://tiles.windy.com/tiles/v10.0/darkmap-retina/{z}/{x}/{y}.png", 101 | "image/png", 102 | ); 103 | 104 | const windy_outdoor = new MapLayer( 105 | "Windy - Outdoor", 106 | "Windy - Outdoor", 107 | "windy_outdoor", 108 | mercator_bbox, 109 | web_mercator_quad_hd.clone(), 110 | "https://tiles.windy.com/v1/maptiles/outdoor/256@2x/{z}/{x}/{y}/", 111 | "image/png", 112 | ); 113 | 114 | const windy_winter = new MapLayer( 115 | "Windy - Winter", 116 | "Windy - Outdoor", 117 | "windy_outdoor", 118 | mercator_bbox, 119 | web_mercator_quad.clone(), 120 | "https://tiles.windy.com/v1/maptiles/winter/256/{z}/{x}/{y}/", 121 | "image/png", 122 | ); 123 | 124 | const windy = [windy_dark, windy_outdoor, windy_winter]; 125 | 126 | export const layers = [osm, osm_de, osmand, f4map_2d, ...carto, ...windy]; 127 | 128 | export const service: Service = { 129 | title: "OpenStreetMap", 130 | abstract: "OpenStreetMap WMTS", 131 | keywords: ["OpenStreetMap", "osm"], 132 | }; 133 | 134 | export const cap = new Capabilities(service, layers).xml; 135 | --------------------------------------------------------------------------------