|
@@ -1,13 +1,171 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div class="appMapViewer" ref="appMap">
|
|
|
|
|
- <filter id="glow"
|
|
|
|
|
- ><feGaussianBlur stdDeviation="4" result="coloredBlur" /><feMerge
|
|
|
|
|
- ><feMergeNode in="coloredBlur" /><feMergeNode in="SourceGraphic" /></feMerge
|
|
|
|
|
- ></filter>
|
|
|
|
|
|
|
+ <div class="appMapViewer">
|
|
|
|
|
+ <svg
|
|
|
|
|
+ class="appMapBoundaryFilters"
|
|
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
|
|
+ width="0"
|
|
|
|
|
+ height="0"
|
|
|
|
|
+ aria-hidden="true"
|
|
|
|
|
+ >
|
|
|
|
|
+ <defs>
|
|
|
|
|
+ <filter
|
|
|
|
|
+ id="industry-map-boundary-glow-district"
|
|
|
|
|
+ x="-60%"
|
|
|
|
|
+ y="-60%"
|
|
|
|
|
+ width="220%"
|
|
|
|
|
+ height="220%"
|
|
|
|
|
+ color-interpolation-filters="sRGB"
|
|
|
|
|
+ >
|
|
|
|
|
+ <feGaussianBlur in="SourceGraphic" stdDeviation="3" result="blur" />
|
|
|
|
|
+ <feFlood
|
|
|
|
|
+ id="industry-map-boundary-flood-district"
|
|
|
|
|
+ flood-color="#78FF37"
|
|
|
|
|
+ flood-opacity="0.82"
|
|
|
|
|
+ result="flood"
|
|
|
|
|
+ />
|
|
|
|
|
+ <feComposite in="flood" in2="blur" operator="in" result="glow" />
|
|
|
|
|
+ <feMerge>
|
|
|
|
|
+ <feMergeNode in="glow" />
|
|
|
|
|
+ <feMergeNode in="SourceGraphic" />
|
|
|
|
|
+ </feMerge>
|
|
|
|
|
+ </filter>
|
|
|
|
|
+ <filter
|
|
|
|
|
+ id="industry-map-boundary-glow-town"
|
|
|
|
|
+ x="-60%"
|
|
|
|
|
+ y="-60%"
|
|
|
|
|
+ width="220%"
|
|
|
|
|
+ height="220%"
|
|
|
|
|
+ color-interpolation-filters="sRGB"
|
|
|
|
|
+ >
|
|
|
|
|
+ <feGaussianBlur in="SourceGraphic" stdDeviation="3" result="blur" />
|
|
|
|
|
+ <feFlood
|
|
|
|
|
+ id="industry-map-boundary-flood-town"
|
|
|
|
|
+ flood-color="#FFF200"
|
|
|
|
|
+ flood-opacity="0.82"
|
|
|
|
|
+ result="flood"
|
|
|
|
|
+ />
|
|
|
|
|
+ <feComposite in="flood" in2="blur" operator="in" result="glow" />
|
|
|
|
|
+ <feMerge>
|
|
|
|
|
+ <feMergeNode in="glow" />
|
|
|
|
|
+ <feMergeNode in="SourceGraphic" />
|
|
|
|
|
+ </feMerge>
|
|
|
|
|
+ </filter>
|
|
|
|
|
+ <filter
|
|
|
|
|
+ id="industry-map-boundary-glow-park"
|
|
|
|
|
+ x="-60%"
|
|
|
|
|
+ y="-60%"
|
|
|
|
|
+ width="220%"
|
|
|
|
|
+ height="220%"
|
|
|
|
|
+ color-interpolation-filters="sRGB"
|
|
|
|
|
+ >
|
|
|
|
|
+ <feGaussianBlur in="SourceGraphic" stdDeviation="3" result="blur" />
|
|
|
|
|
+ <feFlood
|
|
|
|
|
+ id="industry-map-boundary-flood-park"
|
|
|
|
|
+ flood-color="#D860FF"
|
|
|
|
|
+ flood-opacity="0.82"
|
|
|
|
|
+ result="flood"
|
|
|
|
|
+ />
|
|
|
|
|
+ <feComposite in="flood" in2="blur" operator="in" result="glow" />
|
|
|
|
|
+ <feMerge>
|
|
|
|
|
+ <feMergeNode in="glow" />
|
|
|
|
|
+ <feMergeNode in="SourceGraphic" />
|
|
|
|
|
+ </feMerge>
|
|
|
|
|
+ </filter>
|
|
|
|
|
+ </defs>
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ <div class="appMapLeaflet" ref="appMap"></div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
<script>
|
|
<script>
|
|
|
import publicFun from "@/utils/publicFunction.js";
|
|
import publicFun from "@/utils/publicFunction.js";
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 地图缩放上限:高于各底图 maxNativeZoom 时由 Leaflet 拉伸最后一级瓦片。
|
|
|
|
|
+ * 取 22 以便在原生 19 级外仍可多级放大视图(略糊但不断图)。
|
|
|
|
|
+ */
|
|
|
|
|
+const MAP_MIN_ZOOM = 9;
|
|
|
|
|
+const MAP_MAX_ZOOM = 22;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 暗蓝色政务底图 shmap_blue_web:原生最高 z(青浦园心附近瓦片 z=19 为 PNG 200,z=20 为 404)。
|
|
|
|
|
+ */
|
|
|
|
|
+const SHMAP_BLUE_WEB_MAX_NATIVE_ZOOM = 19;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 标准版政务底图 shmap_normal_web:与 blue 同测,最高至 19。
|
|
|
|
|
+ */
|
|
|
|
|
+const SHMAP_NORMAL_WEB_MAX_NATIVE_ZOOM = 19;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Esri World Imagery:REST 元数据 lods 可到 23,本项目业务实测有效影像至 19;再高多为占位/拉伸,按实测设为 19。
|
|
|
|
|
+ */
|
|
|
|
|
+const ARCGIS_WORLD_IMAGERY_MAX_NATIVE_ZOOM = 19;
|
|
|
|
|
+
|
|
|
|
|
+/** 天地图路网 WMTS(与政务底图相同方式拼接 proxyToken) */
|
|
|
|
|
+const ROAD_WMTS_PROXY_BASE =
|
|
|
|
|
+ "http://121.43.55.7:10011/proxy/?servertype=tdt_yxzj&proxyToken=";
|
|
|
|
|
+
|
|
|
|
|
+/** 路网瓦片原生级别上限(可按服务调整) */
|
|
|
|
|
+const ROAD_WMTS_MAX_NATIVE_ZOOM = 18;
|
|
|
|
|
+
|
|
|
|
|
+/** 若为 true,则 TILEROW 按 TMS 与 XYZ 互转(部分天地图 WMTS 需要) */
|
|
|
|
|
+const ROAD_WMTS_TMS_ROW = false;
|
|
|
|
|
+
|
|
|
|
|
+/** 与 qpjyj 图层控制中名称一致,无颜色配置 */
|
|
|
|
|
+const ROAD_ANNOTATION_LAYER_NAME = "影像注记图层";
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Leaflet XYZ 瓦片 → OGC WMTS GetTile(与 Cesium WebMapTileServiceImageryProvider 同类参数)
|
|
|
|
|
+ */
|
|
|
|
|
+function createRoadWmtsLayer(L, baseUrlWithToken, options) {
|
|
|
|
|
+ const Layer = L.TileLayer.extend({
|
|
|
|
|
+ initialize: function (baseUrl, opts) {
|
|
|
|
|
+ this._roadBaseUrl = baseUrl;
|
|
|
|
|
+ L.TileLayer.prototype.initialize.call(this, "", opts);
|
|
|
|
|
+ },
|
|
|
|
|
+ getTileUrl: function (coords) {
|
|
|
|
|
+ const z = coords.z;
|
|
|
|
|
+ const x = coords.x;
|
|
|
|
|
+ const y = ROAD_WMTS_TMS_ROW ? Math.pow(2, z) - 1 - coords.y : coords.y;
|
|
|
|
|
+ return (
|
|
|
|
|
+ this._roadBaseUrl +
|
|
|
|
|
+ "&SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&STYLE=default&FORMAT=tiles" +
|
|
|
|
|
+ "&TILEMATRIX=" +
|
|
|
|
|
+ z +
|
|
|
|
|
+ "&TILEROW=" +
|
|
|
|
|
+ y +
|
|
|
|
|
+ "&TILECOL=" +
|
|
|
|
|
+ x
|
|
|
|
|
+ );
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ return new Layer(baseUrlWithToken, options);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const BOUNDARY_LAYER_LINE_CSS = {
|
|
|
|
|
+ 区县行政边界: "--ui-layer-boundary-district",
|
|
|
|
|
+ 乡镇行政边界: "--ui-layer-boundary-town",
|
|
|
|
|
+ 园区范围边界: "--ui-layer-boundary-park",
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const BOUNDARY_GLOW_FILTER = {
|
|
|
|
|
+ 区县行政边界: "url(#industry-map-boundary-glow-district)",
|
|
|
|
|
+ 乡镇行政边界: "url(#industry-map-boundary-glow-town)",
|
|
|
|
|
+ 园区范围边界: "url(#industry-map-boundary-glow-park)",
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const BOUNDARY_FLOOD_ID = {
|
|
|
|
|
+ 区县行政边界: "industry-map-boundary-flood-district",
|
|
|
|
|
+ 乡镇行政边界: "industry-map-boundary-flood-town",
|
|
|
|
|
+ 园区范围边界: "industry-map-boundary-flood-park",
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const DEFAULT_BOUNDARY_LINE = {
|
|
|
|
|
+ 区县行政边界: "#78FF37",
|
|
|
|
|
+ 乡镇行政边界: "#FFF200",
|
|
|
|
|
+ 园区范围边界: "#D860FF",
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
export default {
|
|
export default {
|
|
|
name: "appMap",
|
|
name: "appMap",
|
|
|
data() {
|
|
data() {
|
|
@@ -57,7 +215,11 @@ export default {
|
|
|
url:
|
|
url:
|
|
|
"https://szlszxdt.qpservice.org.cn/internal_map//tile/{z}/{y}/{x}?servertype=shmap_blue_web&proxyToken=" +
|
|
"https://szlszxdt.qpservice.org.cn/internal_map//tile/{z}/{y}/{x}?servertype=shmap_blue_web&proxyToken=" +
|
|
|
localStorage.getItem("TOKEN"),
|
|
localStorage.getItem("TOKEN"),
|
|
|
- options: {},
|
|
|
|
|
|
|
+ options: {
|
|
|
|
|
+ minZoom: MAP_MIN_ZOOM,
|
|
|
|
|
+ maxZoom: MAP_MAX_ZOOM,
|
|
|
|
|
+ maxNativeZoom: SHMAP_BLUE_WEB_MAX_NATIVE_ZOOM,
|
|
|
|
|
+ },
|
|
|
},
|
|
},
|
|
|
// shmap_grey_web: {
|
|
// shmap_grey_web: {
|
|
|
// name: "浅灰色底图",
|
|
// name: "浅灰色底图",
|
|
@@ -73,7 +235,11 @@ export default {
|
|
|
url:
|
|
url:
|
|
|
"https://szlszxdt.qpservice.org.cn/internal_map//tile/{z}/{y}/{x}?servertype=shmap_normal_web&proxyToken=" +
|
|
"https://szlszxdt.qpservice.org.cn/internal_map//tile/{z}/{y}/{x}?servertype=shmap_normal_web&proxyToken=" +
|
|
|
localStorage.getItem("TOKEN"),
|
|
localStorage.getItem("TOKEN"),
|
|
|
- options: {},
|
|
|
|
|
|
|
+ options: {
|
|
|
|
|
+ minZoom: MAP_MIN_ZOOM,
|
|
|
|
|
+ maxZoom: MAP_MAX_ZOOM,
|
|
|
|
|
+ maxNativeZoom: SHMAP_NORMAL_WEB_MAX_NATIVE_ZOOM,
|
|
|
|
|
+ },
|
|
|
},
|
|
},
|
|
|
// shmap_base_web: {
|
|
// shmap_base_web: {
|
|
|
// name: "常规色底图",
|
|
// name: "常规色底图",
|
|
@@ -89,13 +255,19 @@ export default {
|
|
|
type: "tile",
|
|
type: "tile",
|
|
|
url:
|
|
url:
|
|
|
"https://server.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
|
"https://server.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
|
|
- options: {},
|
|
|
|
|
|
|
+ options: {
|
|
|
|
|
+ minZoom: MAP_MIN_ZOOM,
|
|
|
|
|
+ maxZoom: MAP_MAX_ZOOM,
|
|
|
|
|
+ maxNativeZoom: ARCGIS_WORLD_IMAGERY_MAX_NATIVE_ZOOM,
|
|
|
|
|
+ },
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
// 当前激活的底图
|
|
// 当前激活的底图
|
|
|
activeBaseMap: "shmap_blue_web",
|
|
activeBaseMap: "shmap_blue_web",
|
|
|
// 底图图层实例
|
|
// 底图图层实例
|
|
|
baseMapLayers: {},
|
|
baseMapLayers: {},
|
|
|
|
|
+ /** 路网 WMTS,固定叠在底图之上、图层控制矢量之下(见 pane z-index) */
|
|
|
|
|
+ roadOverlayLayer: null,
|
|
|
map: "",
|
|
map: "",
|
|
|
layers: "",
|
|
layers: "",
|
|
|
marker: "",
|
|
marker: "",
|
|
@@ -112,7 +284,7 @@ export default {
|
|
|
// 边界
|
|
// 边界
|
|
|
区县行政边界: {},
|
|
区县行政边界: {},
|
|
|
乡镇行政边界: {},
|
|
乡镇行政边界: {},
|
|
|
- 所有园区范围边界: {},
|
|
|
|
|
|
|
+ 园区范围边界: {},
|
|
|
// 地理元素
|
|
// 地理元素
|
|
|
水系: null,
|
|
水系: null,
|
|
|
绿地: null,
|
|
绿地: null,
|
|
@@ -120,11 +292,6 @@ export default {
|
|
|
城市路网: null,
|
|
城市路网: null,
|
|
|
交通枢纽: null,
|
|
交通枢纽: null,
|
|
|
},
|
|
},
|
|
|
- layerColor: {
|
|
|
|
|
- 区县行政边界: "#67C23A",
|
|
|
|
|
- 乡镇行政边界: "#E6A23C",
|
|
|
|
|
- 所有园区范围边界: "#409EFF",
|
|
|
|
|
- },
|
|
|
|
|
// 图例类型配置
|
|
// 图例类型配置
|
|
|
legendsTypes: {
|
|
legendsTypes: {
|
|
|
医药器械制造: {
|
|
医药器械制造: {
|
|
@@ -167,18 +334,109 @@ export default {
|
|
|
props: [],
|
|
props: [],
|
|
|
destroy() {},
|
|
destroy() {},
|
|
|
methods: {
|
|
methods: {
|
|
|
|
|
+ getBoundaryLineColor(title) {
|
|
|
|
|
+ const key = BOUNDARY_LAYER_LINE_CSS[title];
|
|
|
|
|
+ if (!key) return DEFAULT_BOUNDARY_LINE[title] || "#409EFF";
|
|
|
|
|
+ const raw = getComputedStyle(document.documentElement).getPropertyValue(key).trim();
|
|
|
|
|
+ if (raw) return raw;
|
|
|
|
|
+ return DEFAULT_BOUNDARY_LINE[title] || "#409EFF";
|
|
|
|
|
+ },
|
|
|
|
|
+ parseCssColorToRgb(css) {
|
|
|
|
|
+ if (css == null) return null;
|
|
|
|
|
+ const s = String(css).trim();
|
|
|
|
|
+ if (!s) return null;
|
|
|
|
|
+ if (s.startsWith("#")) {
|
|
|
|
|
+ let h = s.slice(1);
|
|
|
|
|
+ if (h.length === 3) {
|
|
|
|
|
+ h = h
|
|
|
|
|
+ .split("")
|
|
|
|
|
+ .map((c) => c + c)
|
|
|
|
|
+ .join("");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (h.length >= 6) {
|
|
|
|
|
+ const n = parseInt(h.slice(0, 6), 16);
|
|
|
|
|
+ if (!Number.isNaN(n)) {
|
|
|
|
|
+ return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ const m = s.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
|
|
|
|
|
+ if (m) {
|
|
|
|
|
+ return { r: +m[1], g: +m[2], b: +m[3] };
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ },
|
|
|
|
|
+ /** feFlood 使用 rgb();与线条同色,内发光随线条色联动 */
|
|
|
|
|
+ boundaryLineToFloodColor(css) {
|
|
|
|
|
+ const rgb = this.parseCssColorToRgb(css);
|
|
|
|
|
+ if (!rgb) return "#409EFF";
|
|
|
|
|
+ return `rgb(${rgb.r},${rgb.g},${rgb.b})`;
|
|
|
|
|
+ },
|
|
|
|
|
+ boundaryFillColor(css) {
|
|
|
|
|
+ const rgb = this.parseCssColorToRgb(css);
|
|
|
|
|
+ if (!rgb) return "rgba(64, 158, 255, 0.07)";
|
|
|
|
|
+ return `rgba(${rgb.r},${rgb.g},${rgb.b},0.07)`;
|
|
|
|
|
+ },
|
|
|
|
|
+ updateBoundaryGlowFloods() {
|
|
|
|
|
+ const titles = Object.keys(BOUNDARY_FLOOD_ID);
|
|
|
|
|
+ titles.forEach((title) => {
|
|
|
|
|
+ const fid = BOUNDARY_FLOOD_ID[title];
|
|
|
|
|
+ const el = typeof document !== "undefined" ? document.getElementById(fid) : null;
|
|
|
|
|
+ if (!el) return;
|
|
|
|
|
+ const line = this.getBoundaryLineColor(title);
|
|
|
|
|
+ el.setAttribute("flood-color", this.boundaryLineToFloodColor(line));
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ /** 主题或底图模式切换后刷新边界样式与标签颜色 */
|
|
|
|
|
+ refreshBoundaryPolygonStyles() {
|
|
|
|
|
+ this.updateBoundaryGlowFloods();
|
|
|
|
|
+ const boundaryTitles = Object.keys(BOUNDARY_LAYER_LINE_CSS);
|
|
|
|
|
+ boundaryTitles.forEach((title) => {
|
|
|
|
|
+ const color = this.getBoundaryLineColor(title);
|
|
|
|
|
+ const fill = this.boundaryFillColor(color);
|
|
|
|
|
+ const flt = BOUNDARY_GLOW_FILTER[title];
|
|
|
|
|
+ const polys = this.layerControlPolygon[title];
|
|
|
|
|
+ if (!polys || typeof polys !== "object") return;
|
|
|
|
|
+ Object.keys(polys).forEach((key) => {
|
|
|
|
|
+ const polygon = polys[key];
|
|
|
|
|
+ if (polygon && polygon.setStyle) {
|
|
|
|
|
+ polygon.setStyle({
|
|
|
|
|
+ color,
|
|
|
|
|
+ weight: 2,
|
|
|
|
|
+ fillColor: fill,
|
|
|
|
|
+ opacity: 1,
|
|
|
|
|
+ fillOpacity: 0.7,
|
|
|
|
|
+ filter: flt,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ const marker = this.markers["polygon"] && this.markers["polygon"][key];
|
|
|
|
|
+ if (marker && marker.options && marker.options.icon && key !== "青浦区") {
|
|
|
|
|
+ marker.setIcon(
|
|
|
|
|
+ L.divIcon({
|
|
|
|
|
+ html: `<div>
|
|
|
|
|
+ <div class="title" style="color:${color};">${key}</div>
|
|
|
|
|
+ </div>`,
|
|
|
|
|
+ className: "mapParkName2",
|
|
|
|
|
+ iconSize: 25,
|
|
|
|
|
+ })
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
mapInit() {
|
|
mapInit() {
|
|
|
let center_ = [31.146179026117824, 121.11121627562943];
|
|
let center_ = [31.146179026117824, 121.11121627562943];
|
|
|
this.map = L.map(this.$refs.appMap, {
|
|
this.map = L.map(this.$refs.appMap, {
|
|
|
center: center_,
|
|
center: center_,
|
|
|
zoom: 15,
|
|
zoom: 15,
|
|
|
- minZoom: 9,
|
|
|
|
|
- maxZoom: 18,
|
|
|
|
|
|
|
+ minZoom: MAP_MIN_ZOOM,
|
|
|
|
|
+ maxZoom: MAP_MAX_ZOOM,
|
|
|
zoomControl: true,
|
|
zoomControl: true,
|
|
|
attributionControl: false,
|
|
attributionControl: false,
|
|
|
doubleClickZoom: false,
|
|
doubleClickZoom: false,
|
|
|
});
|
|
});
|
|
|
- // 加载初始底图
|
|
|
|
|
|
|
+ this.initLeafletPanes();
|
|
|
|
|
+ // 加载初始底图(末尾会挂上路网叠加层)
|
|
|
this.loadBaseMap(this.activeBaseMap);
|
|
this.loadBaseMap(this.activeBaseMap);
|
|
|
// 移除缩放控件
|
|
// 移除缩放控件
|
|
|
this.map.removeControl(this.map.zoomControl);
|
|
this.map.removeControl(this.map.zoomControl);
|
|
@@ -213,6 +471,45 @@ export default {
|
|
|
});
|
|
});
|
|
|
// emit 事件
|
|
// emit 事件
|
|
|
this.$emit("mapInit", true);
|
|
this.$emit("mapInit", true);
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ this.updateBoundaryGlowFloods();
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ /** 图层层级:tilePane 默认 200;底图 200;路网 350;矢量/GeoJSON 默认 overlayPane 400 */
|
|
|
|
|
+ initLeafletPanes() {
|
|
|
|
|
+ if (!this.map || this._leafletPanesInited) return;
|
|
|
|
|
+ this._leafletPanesInited = true;
|
|
|
|
|
+ this.map.createPane("basemap");
|
|
|
|
|
+ this.map.getPane("basemap").style.zIndex = 200;
|
|
|
|
|
+ this.map.createPane("roadOverlay");
|
|
|
|
|
+ this.map.getPane("roadOverlay").style.zIndex = 350;
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ roadWmtsBaseUrl() {
|
|
|
|
|
+ return ROAD_WMTS_PROXY_BASE + (localStorage.getItem("TOKEN") || "");
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ /** 创建/更新路网 WMTS;显隐仅由 changeLayerControl(影像注记图层) 控制,此处不强制 addTo(避免关闭后被切换底图再次打开) */
|
|
|
|
|
+ ensureRoadOverlayLayer() {
|
|
|
|
|
+ if (!this.map) return;
|
|
|
|
|
+ const base = this.roadWmtsBaseUrl();
|
|
|
|
|
+ if (this.roadOverlayLayer) {
|
|
|
|
|
+ this.roadOverlayLayer._roadBaseUrl = base;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.roadOverlayLayer = createRoadWmtsLayer(L, base, {
|
|
|
|
|
+ pane: "roadOverlay",
|
|
|
|
|
+ minZoom: MAP_MIN_ZOOM,
|
|
|
|
|
+ maxZoom: MAP_MAX_ZOOM,
|
|
|
|
|
+ maxNativeZoom: ROAD_WMTS_MAX_NATIVE_ZOOM,
|
|
|
|
|
+ opacity: 1,
|
|
|
|
|
+ });
|
|
|
|
|
+ this.roadOverlayLayer.on("error", (e) => {
|
|
|
|
|
+ console.warn("路网 WMTS 瓦片加载异常:", e && e.tile ? e.tile.src : e);
|
|
|
|
|
+ });
|
|
|
|
|
+ /* 默认与侧栏「影像注记图层」开关一致为开;显隐仍以 changeLayerControl 为准 */
|
|
|
|
|
+ this.roadOverlayLayer.addTo(this.map);
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
// 加载底图
|
|
// 加载底图
|
|
@@ -238,11 +535,21 @@ export default {
|
|
|
});
|
|
});
|
|
|
break;
|
|
break;
|
|
|
case "tile":
|
|
case "tile":
|
|
|
- layer = L.tileLayer(service.url, service.options);
|
|
|
|
|
|
|
+ layer = L.tileLayer(service.url, {
|
|
|
|
|
+ pane: "basemap",
|
|
|
|
|
+ minZoom: MAP_MIN_ZOOM,
|
|
|
|
|
+ maxZoom: MAP_MAX_ZOOM,
|
|
|
|
|
+ ...(service.options || {}),
|
|
|
|
|
+ });
|
|
|
break;
|
|
break;
|
|
|
// 支持更多底图类型...
|
|
// 支持更多底图类型...
|
|
|
case "wms":
|
|
case "wms":
|
|
|
- layer = L.tileLayer.wms(service.url, service.options);
|
|
|
|
|
|
|
+ layer = L.tileLayer.wms(service.url, {
|
|
|
|
|
+ pane: "basemap",
|
|
|
|
|
+ minZoom: MAP_MIN_ZOOM,
|
|
|
|
|
+ maxZoom: MAP_MAX_ZOOM,
|
|
|
|
|
+ ...(service.options || {}),
|
|
|
|
|
+ });
|
|
|
break;
|
|
break;
|
|
|
default:
|
|
default:
|
|
|
console.error("不支持的底图类型:", service.type);
|
|
console.error("不支持的底图类型:", service.type);
|
|
@@ -264,6 +571,8 @@ export default {
|
|
|
console.error(`${service.name} 加载失败:`, err);
|
|
console.error(`${service.name} 加载失败:`, err);
|
|
|
// 可以添加用户提示
|
|
// 可以添加用户提示
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+ this.ensureRoadOverlayLayer();
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error("加载底图失败:", error);
|
|
console.error("加载底图失败:", error);
|
|
|
}
|
|
}
|
|
@@ -369,6 +678,39 @@ export default {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
+ escapeHtml(str) {
|
|
|
|
|
+ if (str == null || str === undefined) return "";
|
|
|
|
|
+ return String(str)
|
|
|
|
|
+ .replace(/&/g, "&")
|
|
|
|
|
+ .replace(/</g, "<")
|
|
|
|
|
+ .replace(/>/g, ">")
|
|
|
|
|
+ .replace(/"/g, """);
|
|
|
|
|
+ },
|
|
|
|
|
+ buildParkBoundaryPopupHtml(data) {
|
|
|
|
|
+ const b = data["四至边界"];
|
|
|
|
|
+ if (!b || typeof b !== "object") return "";
|
|
|
|
|
+ const keys = ["东", "南", "西", "北"];
|
|
|
|
|
+ const rows = keys
|
|
|
|
|
+ .map((k) => {
|
|
|
|
|
+ const v = (b[k] != null && String(b[k]).trim()) || "";
|
|
|
|
|
+ if (!v) return "";
|
|
|
|
|
+ return `<div class="map-popup-boundary-row"><span class="map-popup-boundary-k">${k}</span><span class="map-popup-boundary-v">${this.escapeHtml(
|
|
|
|
|
+ v
|
|
|
|
|
+ )}</span></div>`;
|
|
|
|
|
+ })
|
|
|
|
|
+ .filter(Boolean)
|
|
|
|
|
+ .join("");
|
|
|
|
|
+ if (!rows) return "";
|
|
|
|
|
+ return `<div class="map-popup-boundary map-popup-intro"><div class="title2">四至边界</div>${rows}</div>`;
|
|
|
|
|
+ },
|
|
|
|
|
+ buildParkExcelRemarkPopupHtml(data) {
|
|
|
|
|
+ const raw = data.properties && data.properties.excel备注;
|
|
|
|
|
+ const r = raw != null && String(raw).trim();
|
|
|
|
|
+ if (!r) return "";
|
|
|
|
|
+ return `<div class="map-popup-intro map-popup-excel-remark"><div class="title2">备注</div><div class="map-popup-intro-text">${this.escapeHtml(
|
|
|
|
|
+ raw
|
|
|
|
|
+ )}</div></div>`;
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
// 定位到当前位置
|
|
// 定位到当前位置
|
|
|
panToLocation(item, zoom) {
|
|
panToLocation(item, zoom) {
|
|
@@ -392,20 +734,21 @@ export default {
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
|
// 经纬度位置清洗
|
|
// 经纬度位置清洗
|
|
|
let polygon = L.geoJSON(data).addTo(this.map);
|
|
let polygon = L.geoJSON(data).addTo(this.map);
|
|
|
|
|
+ const lineColor = this.getBoundaryLineColor(title);
|
|
|
polygon.setStyle({
|
|
polygon.setStyle({
|
|
|
- color: this.layerColor[title],
|
|
|
|
|
|
|
+ color: lineColor,
|
|
|
weight: 2,
|
|
weight: 2,
|
|
|
- fillColor: this.layerColor[title] + "12",
|
|
|
|
|
|
|
+ fillColor: this.boundaryFillColor(lineColor),
|
|
|
opacity: 1,
|
|
opacity: 1,
|
|
|
fillOpacity: 0.7,
|
|
fillOpacity: 0.7,
|
|
|
- filter: "url(#glow)", // 应用滤镜
|
|
|
|
|
|
|
+ filter: BOUNDARY_GLOW_FILTER[title] || BOUNDARY_GLOW_FILTER["园区范围边界"],
|
|
|
});
|
|
});
|
|
|
// 同时显示园区文字提示
|
|
// 同时显示园区文字提示
|
|
|
if (data.title != "青浦区") {
|
|
if (data.title != "青浦区") {
|
|
|
let marker = L.marker(polygon.getBounds().getCenter(), {
|
|
let marker = L.marker(polygon.getBounds().getCenter(), {
|
|
|
icon: L.divIcon({
|
|
icon: L.divIcon({
|
|
|
html: `<div>
|
|
html: `<div>
|
|
|
- <div class="title" style="color:${this.layerColor[title]};">${data.title}</div>
|
|
|
|
|
|
|
+ <div class="title" style="color:${this.getBoundaryLineColor(title)};">${data.title}</div>
|
|
|
</div>`,
|
|
</div>`,
|
|
|
className: "mapParkName2",
|
|
className: "mapParkName2",
|
|
|
iconSize: 25,
|
|
iconSize: 25,
|
|
@@ -415,7 +758,7 @@ export default {
|
|
|
this.markers["polygon"] = {};
|
|
this.markers["polygon"] = {};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (title == "所有园区范围边界") {
|
|
|
|
|
|
|
+ if (title == "园区范围边界") {
|
|
|
marker.on("click", (e) => {
|
|
marker.on("click", (e) => {
|
|
|
this.$emit("changePark", data);
|
|
this.$emit("changePark", data);
|
|
|
});
|
|
});
|
|
@@ -423,27 +766,50 @@ export default {
|
|
|
this.markers["polygon"][data.title] = marker;
|
|
this.markers["polygon"][data.title] = marker;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (title == "所有园区范围边界") {
|
|
|
|
|
|
|
+ if (title == "园区范围边界") {
|
|
|
|
|
+ const boundaryHtml = this.buildParkBoundaryPopupHtml(data);
|
|
|
|
|
+ const remarkHtml = this.buildParkExcelRemarkPopupHtml(data);
|
|
|
|
|
+ const introText = this.parkInfo[data.title] || "";
|
|
|
// 中间区域添加一个marker点并绑定title文字
|
|
// 中间区域添加一个marker点并绑定title文字
|
|
|
let popupHtml = `<div class="mapParkName_box">
|
|
let popupHtml = `<div class="mapParkName_box">
|
|
|
- <div class="title">${data.title}<span>${data.properties.area}</span></div>
|
|
|
|
|
|
|
+ <div class="title">${this.escapeHtml(data.title)}<span>${this.escapeHtml(
|
|
|
|
|
+ data.properties.area
|
|
|
|
|
+ )}</span></div>
|
|
|
<div class="card">
|
|
<div class="card">
|
|
|
<img class="map-popup-stat-icon" src="/static/images/pup1.png" style="width:36px;height:36px"/>
|
|
<img class="map-popup-stat-icon" src="/static/images/pup1.png" style="width:36px;height:36px"/>
|
|
|
- <div><div>主导产业</div>
|
|
|
|
|
- <div>${data.主导产业 ? data.主导产业 : "--"}</div>
|
|
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div>主导产业</div>
|
|
|
|
|
+ <div>${this.escapeHtml(data.主导产业 ? data.主导产业 : "--")}</div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div class="card">
|
|
|
|
|
+ <img class="map-popup-stat-icon" src="/static/images/pup2.png" style="width:36px;height:36px"/>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div>规上工业服务业企业数量</div>
|
|
|
|
|
+ <div>${
|
|
|
|
|
+ data["规上工业服务业企业数量"] != null
|
|
|
|
|
+ ? this.escapeHtml(String(data["规上工业服务业企业数量"]))
|
|
|
|
|
+ : "--"
|
|
|
|
|
+ }</div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="card">
|
|
<div class="card">
|
|
|
<img class="map-popup-stat-icon" src="/static/images/pup2.png" style="width:36px;height:36px"/>
|
|
<img class="map-popup-stat-icon" src="/static/images/pup2.png" style="width:36px;height:36px"/>
|
|
|
<div>
|
|
<div>
|
|
|
- <div>入驻企业数量(家)</div>
|
|
|
|
|
- <div>${data.企业数量 ? data.企业数量 : "--"}</div>
|
|
|
|
|
|
|
+ <div>高企数量</div>
|
|
|
|
|
+ <div>${
|
|
|
|
|
+ data["高企数量"] != null
|
|
|
|
|
+ ? this.escapeHtml(String(data["高企数量"]))
|
|
|
|
|
+ : "--"
|
|
|
|
|
+ }</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ ${boundaryHtml}
|
|
|
<div class="map-popup-intro">
|
|
<div class="map-popup-intro">
|
|
|
<div class="title2">园区简介</div>
|
|
<div class="title2">园区简介</div>
|
|
|
- <div class="map-popup-intro-text">${this.parkInfo[data.title]}</div>
|
|
|
|
|
|
|
+ <div class="map-popup-intro-text">${this.escapeHtml(introText)}</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ ${remarkHtml}
|
|
|
</div>`;
|
|
</div>`;
|
|
|
polygon.bindPopup(popupHtml);
|
|
polygon.bindPopup(popupHtml);
|
|
|
polygon.on("click", (e) => {
|
|
polygon.on("click", (e) => {
|
|
@@ -473,6 +839,18 @@ export default {
|
|
|
},
|
|
},
|
|
|
// 切换图层控制显示
|
|
// 切换图层控制显示
|
|
|
changeLayerControl(item) {
|
|
changeLayerControl(item) {
|
|
|
|
|
+ if (item.name === ROAD_ANNOTATION_LAYER_NAME) {
|
|
|
|
|
+ this.ensureRoadOverlayLayer();
|
|
|
|
|
+ if (!this.roadOverlayLayer || !this.map) return;
|
|
|
|
|
+ if (item.state) {
|
|
|
|
|
+ if (!this.map.hasLayer(this.roadOverlayLayer)) {
|
|
|
|
|
+ this.roadOverlayLayer.addTo(this.map);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (this.map.hasLayer(this.roadOverlayLayer)) {
|
|
|
|
|
+ this.map.removeLayer(this.roadOverlayLayer);
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
if (this.layerControlPolygon[item.name]) {
|
|
if (this.layerControlPolygon[item.name]) {
|
|
|
for (const key in this.layerControlPolygon[item.name]) {
|
|
for (const key in this.layerControlPolygon[item.name]) {
|
|
|
if (item.state) {
|
|
if (item.state) {
|
|
@@ -503,8 +881,24 @@ export default {
|
|
|
</script>
|
|
</script>
|
|
|
<style lang="less" scoped>
|
|
<style lang="less" scoped>
|
|
|
.appMapViewer {
|
|
.appMapViewer {
|
|
|
|
|
+ position: relative;
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
height: 100%;
|
|
height: 100%;
|
|
|
background: var(--ui-map-bg, #000);
|
|
background: var(--ui-map-bg, #000);
|
|
|
}
|
|
}
|
|
|
|
|
+/* Leaflet 默认 .leaflet-container 为 #ddd,缩放时空隙会露白;与主题「地图底色」一致 */
|
|
|
|
|
+.appMapViewer /deep/ .leaflet-container {
|
|
|
|
|
+ background: var(--ui-map-bg, #000) !important;
|
|
|
|
|
+}
|
|
|
|
|
+.appMapBoundaryFilters {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ width: 0;
|
|
|
|
|
+ height: 0;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+}
|
|
|
|
|
+.appMapLeaflet {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|