|
|
@@ -115,12 +115,21 @@
|
|
|
<div class="vueJsonEditor_tools">
|
|
|
<span
|
|
|
v-if="jsonData && (jsonData.features || jsonData.geometry)"
|
|
|
- @click="showToMap(jsonData)"
|
|
|
+ @click="showToMap(jsonData, 'input')"
|
|
|
>渲染到地图中</span
|
|
|
>
|
|
|
+ <el-tooltip content="定位到当前入参渲染要素" placement="top">
|
|
|
+ <span
|
|
|
+ v-if="renderStatus.input"
|
|
|
+ @click="locateRenderedGeojson('input')"
|
|
|
+ >
|
|
|
+ 定位
|
|
|
+ </span>
|
|
|
+ </el-tooltip>
|
|
|
<span @click="copyJsonData(jsonData)">copy</span>
|
|
|
</div>
|
|
|
<vue-json-editor
|
|
|
+ :key="'json-input-editor-' + inputEditorKey"
|
|
|
v-model="jsonData"
|
|
|
:value="jsonData"
|
|
|
:show-btns="false"
|
|
|
@@ -151,13 +160,22 @@
|
|
|
backData.content &&
|
|
|
(backData.content.features || backData.content.geometry)
|
|
|
"
|
|
|
- @click="showToMap(backData.content)"
|
|
|
+ @click="showToMap(backData.content, 'output')"
|
|
|
>渲染到地图中</span
|
|
|
>
|
|
|
+ <el-tooltip content="定位到当前返回渲染要素" placement="top">
|
|
|
+ <span
|
|
|
+ v-if="renderStatus.output"
|
|
|
+ @click="locateRenderedGeojson('output')"
|
|
|
+ >
|
|
|
+ 定位
|
|
|
+ </span>
|
|
|
+ </el-tooltip>
|
|
|
<span @click="copyJsonData(backData.content)">copy</span>
|
|
|
</div>
|
|
|
<vue-json-editor
|
|
|
v-if="backData.content"
|
|
|
+ :key="'json-output-editor-' + outputEditorKey"
|
|
|
v-model="backData.content"
|
|
|
:value="backData.content"
|
|
|
@json-change="handleJsonChange2"
|
|
|
@@ -169,6 +187,56 @@
|
|
|
></el-tab-pane>
|
|
|
</el-tabs>
|
|
|
</div>
|
|
|
+ <el-dialog
|
|
|
+ v-model="propertyDialog.visible"
|
|
|
+ title="要素属性"
|
|
|
+ width="420px"
|
|
|
+ draggable
|
|
|
+ :modal="false"
|
|
|
+ append-to-body
|
|
|
+ class="feature-property-dialog"
|
|
|
+ >
|
|
|
+ <div class="feature-property-content">
|
|
|
+ <div class="feature-property-tip">支持直接编辑属性值,失焦后自动同步 JSON。</div>
|
|
|
+ <div
|
|
|
+ class="feature-property-item"
|
|
|
+ v-for="item in propertyDialog.list"
|
|
|
+ :key="'feature-property-' + item.id"
|
|
|
+ >
|
|
|
+ <div class="feature-property-row">
|
|
|
+ <el-input
|
|
|
+ v-model="item.editKey"
|
|
|
+ class="feature-property-input feature-property-key-input"
|
|
|
+ placeholder="属性名"
|
|
|
+ @change="handlePropertyKeyChange(item)"
|
|
|
+ />
|
|
|
+ <el-button
|
|
|
+ type="danger"
|
|
|
+ plain
|
|
|
+ size="small"
|
|
|
+ class="feature-property-delete-btn"
|
|
|
+ @click="deleteProperty(item)"
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <el-input
|
|
|
+ v-model="item.editValue"
|
|
|
+ class="feature-property-input"
|
|
|
+ placeholder="属性值"
|
|
|
+ @change="handlePropertyValueChange(item)"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <el-empty
|
|
|
+ v-if="!propertyDialog.list.length"
|
|
|
+ description="当前要素无属性信息"
|
|
|
+ :image-size="80"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <el-button type="primary" plain @click="addProperty">+ 新增属性</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
<!-- 绘制工具栏 -->
|
|
|
<div
|
|
|
class="toolbar"
|
|
|
@@ -511,6 +579,29 @@ export default {
|
|
|
currentPolygonEntity: null, // 当前要添加镂空的多边形实体
|
|
|
currentPolygonGeometry: null, // 当前要添加镂空的多边形几何对象
|
|
|
tempEntity: null, // 临时预览实体
|
|
|
+ // 地图渲染状态(入参/返回)
|
|
|
+ renderStatus: {
|
|
|
+ input: false,
|
|
|
+ output: false,
|
|
|
+ },
|
|
|
+ // 缓存两侧最近一次成功渲染的geojson,用于定位时恢复
|
|
|
+ renderedGeojsonCache: {
|
|
|
+ input: null,
|
|
|
+ output: null,
|
|
|
+ },
|
|
|
+ currentRenderedSource: "",
|
|
|
+ // 地图点击属性弹窗
|
|
|
+ featurePickHandler: null,
|
|
|
+ inputEditorKey: 0,
|
|
|
+ outputEditorKey: 0,
|
|
|
+ propertyIdSeed: 1,
|
|
|
+ propertyDialog: {
|
|
|
+ visible: false,
|
|
|
+ list: [],
|
|
|
+ source: "",
|
|
|
+ propertiesRef: null,
|
|
|
+ featureIndex: -1,
|
|
|
+ },
|
|
|
};
|
|
|
},
|
|
|
mounted() {
|
|
|
@@ -523,6 +614,10 @@ export default {
|
|
|
if (this.handler) {
|
|
|
this.handler.destroy();
|
|
|
}
|
|
|
+ if (this.featurePickHandler) {
|
|
|
+ this.featurePickHandler.destroy();
|
|
|
+ this.featurePickHandler = null;
|
|
|
+ }
|
|
|
},
|
|
|
watch: {
|
|
|
SceneValue(newVal, oldVal) {
|
|
|
@@ -596,30 +691,346 @@ export default {
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
+ getSourceGeojsonData(source) {
|
|
|
+ return source === "output" ? this.backData.content : this.jsonData;
|
|
|
+ },
|
|
|
// 将用户输入或后台返回的geojson渲染到地图中
|
|
|
- showToMap(geojson) {
|
|
|
+ showToMap(geojson, source = "input") {
|
|
|
+ if (!geojson || (!geojson.features && !geojson.geometry)) {
|
|
|
+ this.$message({
|
|
|
+ message: "当前JSON缺少有效几何信息,无法渲染",
|
|
|
+ type: "warning",
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.currentRenderedSource = source;
|
|
|
// 1. 清除所有地图中的元素
|
|
|
this.clearAllMap();
|
|
|
// 2. 将geojson添加到地图中
|
|
|
this.addToMap(geojson);
|
|
|
+ this.currentRenderedSource = source;
|
|
|
+ this.renderStatus[source] = true;
|
|
|
+ // 仅缓存纯数据,避免响应式对象带来的引用副作用
|
|
|
+ this.renderedGeojsonCache[source] = JSON.parse(JSON.stringify(geojson));
|
|
|
+ this.flyToGeojson(geojson);
|
|
|
+ },
|
|
|
+ // 定位到已渲染要素
|
|
|
+ locateRenderedGeojson(source) {
|
|
|
+ if (!this.renderStatus[source]) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 当前地图不是该来源数据时,优先恢复对应渲染结果,再飞行定位
|
|
|
+ if (this.currentRenderedSource !== source && this.renderedGeojsonCache[source]) {
|
|
|
+ this.showToMap(this.renderedGeojsonCache[source], source);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const sourceGeojson = this.getSourceGeojsonData(source) || this.renderedGeojsonCache[source];
|
|
|
+ this.flyToGeojson(sourceGeojson);
|
|
|
+ },
|
|
|
+ collectGeometryCoordinates(geometry) {
|
|
|
+ if (!geometry || !geometry.coordinates) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ const { type, coordinates } = geometry;
|
|
|
+ const points = [];
|
|
|
+ if (type === "Point") {
|
|
|
+ points.push([coordinates[0], coordinates[1]]);
|
|
|
+ } else if (type === "LineString") {
|
|
|
+ coordinates.forEach((coord) => points.push([coord[0], coord[1]]));
|
|
|
+ } else if (type === "Polygon") {
|
|
|
+ coordinates.forEach((ring) => {
|
|
|
+ ring.forEach((coord) => points.push([coord[0], coord[1]]));
|
|
|
+ });
|
|
|
+ } else if (type === "MultiPolygon") {
|
|
|
+ coordinates.forEach((polygon) => {
|
|
|
+ polygon.forEach((ring) => {
|
|
|
+ ring.forEach((coord) => points.push([coord[0], coord[1]]));
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return points;
|
|
|
+ },
|
|
|
+ collectGeojsonCoordinates(geojson) {
|
|
|
+ if (!geojson) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ if (geojson.features && Array.isArray(geojson.features)) {
|
|
|
+ return geojson.features.flatMap((feature) =>
|
|
|
+ this.collectGeometryCoordinates(feature.geometry)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (geojson.geometry) {
|
|
|
+ return this.collectGeometryCoordinates(geojson.geometry);
|
|
|
+ }
|
|
|
+ return [];
|
|
|
+ },
|
|
|
+ // 飞行定位到geojson范围
|
|
|
+ flyToGeojson(geojson) {
|
|
|
+ if (!viewer || !geojson) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const points = this.collectGeojsonCoordinates(geojson);
|
|
|
+ if (!points.length) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (points.length === 1) {
|
|
|
+ viewer.camera.flyTo({
|
|
|
+ destination: SkyScenery.Cartesian3.fromDegrees(points[0][0], points[0][1], 1200),
|
|
|
+ duration: 1.1,
|
|
|
+ orientation: {
|
|
|
+ heading: viewer.camera.heading,
|
|
|
+ pitch: SkyScenery.Math.toRadians(-65),
|
|
|
+ roll: 0,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ viewer.scene.requestRender();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let minLon = Infinity;
|
|
|
+ let maxLon = -Infinity;
|
|
|
+ let minLat = Infinity;
|
|
|
+ let maxLat = -Infinity;
|
|
|
+ points.forEach((point) => {
|
|
|
+ minLon = Math.min(minLon, point[0]);
|
|
|
+ maxLon = Math.max(maxLon, point[0]);
|
|
|
+ minLat = Math.min(minLat, point[1]);
|
|
|
+ maxLat = Math.max(maxLat, point[1]);
|
|
|
+ });
|
|
|
+ const lonPad = Math.max((maxLon - minLon) * 0.25, 0.0008);
|
|
|
+ const latPad = Math.max((maxLat - minLat) * 0.25, 0.0008);
|
|
|
+ viewer.camera.flyTo({
|
|
|
+ destination: SkyScenery.Rectangle.fromDegrees(
|
|
|
+ minLon - lonPad,
|
|
|
+ minLat - latPad,
|
|
|
+ maxLon + lonPad,
|
|
|
+ maxLat + latPad
|
|
|
+ ),
|
|
|
+ duration: 1.1,
|
|
|
+ });
|
|
|
+ viewer.scene.requestRender();
|
|
|
+ },
|
|
|
+ // 把属性对象格式化为可编辑列表
|
|
|
+ formatFeatureProperties(properties) {
|
|
|
+ if (!properties || typeof properties !== "object") {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ return Object.keys(properties).map((key) => {
|
|
|
+ const rawValue = properties[key];
|
|
|
+ const editValue =
|
|
|
+ rawValue === null || rawValue === undefined
|
|
|
+ ? ""
|
|
|
+ : typeof rawValue === "object"
|
|
|
+ ? JSON.stringify(rawValue)
|
|
|
+ : String(rawValue);
|
|
|
+ return {
|
|
|
+ id: this.propertyIdSeed++,
|
|
|
+ originalKey: key,
|
|
|
+ editKey: key,
|
|
|
+ editValue,
|
|
|
+ };
|
|
|
+ });
|
|
|
+ },
|
|
|
+ parsePropertyValue(inputValue) {
|
|
|
+ if (typeof inputValue !== "string") {
|
|
|
+ return inputValue;
|
|
|
+ }
|
|
|
+ const value = inputValue.trim();
|
|
|
+ if (value === "") {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ if (value === "true") {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (value === "false") {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (value === "null") {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (!isNaN(Number(value))) {
|
|
|
+ return Number(value);
|
|
|
+ }
|
|
|
+ if ((value.startsWith("{") && value.endsWith("}")) || (value.startsWith("[") && value.endsWith("]"))) {
|
|
|
+ try {
|
|
|
+ return JSON.parse(value);
|
|
|
+ } catch (e) {
|
|
|
+ return inputValue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return inputValue;
|
|
|
+ },
|
|
|
+ syncJsonEditorData(source) {
|
|
|
+ if (source === "output") {
|
|
|
+ if (!this.backData.content) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.backData = {
|
|
|
+ ...this.backData,
|
|
|
+ content: JSON.parse(JSON.stringify(this.backData.content)),
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ this.jsonData = JSON.parse(JSON.stringify(this.jsonData));
|
|
|
+ }
|
|
|
+ if (source === "output") {
|
|
|
+ this.outputEditorKey += 1;
|
|
|
+ } else {
|
|
|
+ this.inputEditorKey += 1;
|
|
|
+ }
|
|
|
+ this.renderedGeojsonCache[source] = JSON.parse(
|
|
|
+ JSON.stringify(this.getSourceGeojsonData(source))
|
|
|
+ );
|
|
|
+ const latestGeojson = this.getSourceGeojsonData(source);
|
|
|
+ if (
|
|
|
+ latestGeojson &&
|
|
|
+ this.propertyDialog.visible &&
|
|
|
+ this.propertyDialog.source === source
|
|
|
+ ) {
|
|
|
+ if (
|
|
|
+ this.propertyDialog.featureIndex >= 0 &&
|
|
|
+ latestGeojson.features &&
|
|
|
+ latestGeojson.features[this.propertyDialog.featureIndex]
|
|
|
+ ) {
|
|
|
+ if (!latestGeojson.features[this.propertyDialog.featureIndex].properties) {
|
|
|
+ latestGeojson.features[this.propertyDialog.featureIndex].properties = {};
|
|
|
+ }
|
|
|
+ this.propertyDialog.propertiesRef =
|
|
|
+ latestGeojson.features[this.propertyDialog.featureIndex].properties;
|
|
|
+ } else {
|
|
|
+ if (!latestGeojson.properties) {
|
|
|
+ latestGeojson.properties = {};
|
|
|
+ }
|
|
|
+ this.propertyDialog.propertiesRef = latestGeojson.properties;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ handlePropertyValueChange(item) {
|
|
|
+ if (!this.propertyDialog.propertiesRef || !item) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const currentKey = (item.editKey || "").trim();
|
|
|
+ if (!currentKey) {
|
|
|
+ this.$message({
|
|
|
+ message: "属性名不能为空",
|
|
|
+ type: "warning",
|
|
|
+ });
|
|
|
+ item.editKey = item.originalKey || "";
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.propertyDialog.propertiesRef[currentKey] = this.parsePropertyValue(item.editValue);
|
|
|
+ item.originalKey = currentKey;
|
|
|
+ item.editKey = currentKey;
|
|
|
+ this.syncPropertyToEntity(this.propertyDialog.propertiesRef);
|
|
|
+ this.syncJsonEditorData(this.propertyDialog.source || this.currentRenderedSource || "input");
|
|
|
+ },
|
|
|
+ handlePropertyKeyChange(item) {
|
|
|
+ if (!this.propertyDialog.propertiesRef || !item) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const oldKey = item.originalKey;
|
|
|
+ const newKey = (item.editKey || "").trim();
|
|
|
+ if (!newKey) {
|
|
|
+ this.$message({
|
|
|
+ message: "属性名不能为空",
|
|
|
+ type: "warning",
|
|
|
+ });
|
|
|
+ item.editKey = oldKey;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (newKey !== oldKey && Object.prototype.hasOwnProperty.call(this.propertyDialog.propertiesRef, newKey)) {
|
|
|
+ this.$message({
|
|
|
+ message: "属性名已存在,请更换",
|
|
|
+ type: "warning",
|
|
|
+ });
|
|
|
+ item.editKey = oldKey;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const oldValue = this.propertyDialog.propertiesRef[oldKey];
|
|
|
+ if (newKey !== oldKey) {
|
|
|
+ delete this.propertyDialog.propertiesRef[oldKey];
|
|
|
+ this.propertyDialog.propertiesRef[newKey] = oldValue;
|
|
|
+ }
|
|
|
+ item.originalKey = newKey;
|
|
|
+ item.editKey = newKey;
|
|
|
+ this.syncPropertyToEntity(this.propertyDialog.propertiesRef);
|
|
|
+ this.syncJsonEditorData(this.propertyDialog.source || this.currentRenderedSource || "input");
|
|
|
+ },
|
|
|
+ deleteProperty(item) {
|
|
|
+ if (!this.propertyDialog.propertiesRef || !item) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const key = (item.originalKey || "").trim();
|
|
|
+ if (key && Object.prototype.hasOwnProperty.call(this.propertyDialog.propertiesRef, key)) {
|
|
|
+ delete this.propertyDialog.propertiesRef[key];
|
|
|
+ }
|
|
|
+ this.propertyDialog.list = this.propertyDialog.list.filter((property) => property.id !== item.id);
|
|
|
+ this.syncPropertyToEntity(this.propertyDialog.propertiesRef);
|
|
|
+ this.syncJsonEditorData(this.propertyDialog.source || this.currentRenderedSource || "input");
|
|
|
+ },
|
|
|
+ addProperty() {
|
|
|
+ if (!this.propertyDialog.propertiesRef) {
|
|
|
+ this.$message({
|
|
|
+ message: "请先点击地图中的已渲染要素",
|
|
|
+ type: "warning",
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let index = 1;
|
|
|
+ let newKey = "newKey";
|
|
|
+ while (Object.prototype.hasOwnProperty.call(this.propertyDialog.propertiesRef, newKey)) {
|
|
|
+ newKey = `newKey${index}`;
|
|
|
+ index += 1;
|
|
|
+ }
|
|
|
+ this.propertyDialog.propertiesRef[newKey] = "";
|
|
|
+ this.propertyDialog.list.push({
|
|
|
+ id: this.propertyIdSeed++,
|
|
|
+ originalKey: newKey,
|
|
|
+ editKey: newKey,
|
|
|
+ editValue: "",
|
|
|
+ });
|
|
|
+ this.syncPropertyToEntity(this.propertyDialog.propertiesRef);
|
|
|
+ this.syncJsonEditorData(this.propertyDialog.source || this.currentRenderedSource || "input");
|
|
|
+ },
|
|
|
+ syncPropertyToEntity(propertiesRef) {
|
|
|
+ if (!propertiesRef || !viewer || !this.currentRenderedSource) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const targetSource = this.propertyDialog.source || this.currentRenderedSource;
|
|
|
+ const targetIndex = this.propertyDialog.featureIndex;
|
|
|
+ this.drawnEntities.forEach((entity) => {
|
|
|
+ if (!entity || !entity.__featureRef) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (
|
|
|
+ entity.__featureRef.source === targetSource &&
|
|
|
+ entity.__featureRef.featureIndex === targetIndex
|
|
|
+ ) {
|
|
|
+ entity.__featureProperties = propertiesRef;
|
|
|
+ }
|
|
|
+ });
|
|
|
},
|
|
|
// 将geojson添加到地图中
|
|
|
addToMap(geojson) {
|
|
|
+ const source = this.currentRenderedSource || "input";
|
|
|
if (!geojson.features && geojson.geometry) {
|
|
|
const { type, coordinates } = geojson.geometry;
|
|
|
+ const featureProperties = geojson.properties || {};
|
|
|
+ const featureRefInfo = {
|
|
|
+ source,
|
|
|
+ featureIndex: -1,
|
|
|
+ };
|
|
|
switch (type) {
|
|
|
case "Point":
|
|
|
// 点
|
|
|
- this.addPoint(coordinates);
|
|
|
+ this.addPoint(coordinates, featureProperties, featureRefInfo);
|
|
|
break;
|
|
|
case "LineString":
|
|
|
// 线
|
|
|
- this.addLine(coordinates);
|
|
|
+ this.addLine(coordinates, featureProperties, featureRefInfo);
|
|
|
break;
|
|
|
case "Polygon":
|
|
|
case "MultiPolygon":
|
|
|
// 面
|
|
|
- this.addPolygon(coordinates);
|
|
|
+ this.addPolygon(coordinates, featureProperties, featureRefInfo);
|
|
|
break;
|
|
|
default:
|
|
|
break;
|
|
|
@@ -628,21 +1039,26 @@ export default {
|
|
|
const features = geojson.features;
|
|
|
// 2. 遍历features,根据type添加到地图中
|
|
|
console.log("features", features);
|
|
|
- features.forEach((feature) => {
|
|
|
+ features.forEach((feature, featureIndex) => {
|
|
|
const { type, coordinates } = feature.geometry;
|
|
|
+ const featureProperties = feature.properties || {};
|
|
|
+ const featureRefInfo = {
|
|
|
+ source,
|
|
|
+ featureIndex,
|
|
|
+ };
|
|
|
switch (type) {
|
|
|
case "Point":
|
|
|
// 点
|
|
|
- this.addPoint(coordinates);
|
|
|
+ this.addPoint(coordinates, featureProperties, featureRefInfo);
|
|
|
break;
|
|
|
case "LineString":
|
|
|
// 线
|
|
|
- this.addLine(coordinates);
|
|
|
+ this.addLine(coordinates, featureProperties, featureRefInfo);
|
|
|
break;
|
|
|
case "Polygon":
|
|
|
case "MultiPolygon":
|
|
|
// 面
|
|
|
- this.addPolygon(coordinates);
|
|
|
+ this.addPolygon(coordinates, featureProperties, featureRefInfo);
|
|
|
break;
|
|
|
default:
|
|
|
break;
|
|
|
@@ -654,7 +1070,7 @@ export default {
|
|
|
});
|
|
|
},
|
|
|
// 添加点到地图中
|
|
|
- addPoint(coordinates) {
|
|
|
+ addPoint(coordinates, featureProperties = null, featureRefInfo = null) {
|
|
|
// 1. 解析点的坐标
|
|
|
console.log("addPoint coordinates", coordinates);
|
|
|
// 2. 创建点实体
|
|
|
@@ -676,11 +1092,13 @@ export default {
|
|
|
outlineWidth: 2,
|
|
|
},
|
|
|
});
|
|
|
+ pointEntity.__featureProperties = featureProperties;
|
|
|
+ pointEntity.__featureRef = featureRefInfo;
|
|
|
// 3. 将点实体添加到drawnEntities中
|
|
|
this.drawnEntities.push(pointEntity);
|
|
|
},
|
|
|
// 添加线到地图中
|
|
|
- addLine(coordinates) {
|
|
|
+ addLine(coordinates, featureProperties = null, featureRefInfo = null) {
|
|
|
// 1. 解析线的坐标
|
|
|
console.log("addLine coordinates", coordinates);
|
|
|
// 2. 处理坐标格式:如果是二维数组则取第一个元素,否则直接使用
|
|
|
@@ -716,6 +1134,7 @@ export default {
|
|
|
|
|
|
// 5. 创建线实体
|
|
|
const lineEntity = viewer.entities.add({
|
|
|
+ name: "line",
|
|
|
polyline: {
|
|
|
show: true,
|
|
|
positions: positions,
|
|
|
@@ -723,12 +1142,14 @@ export default {
|
|
|
width: 3,
|
|
|
},
|
|
|
});
|
|
|
+ lineEntity.__featureProperties = featureProperties;
|
|
|
+ lineEntity.__featureRef = featureRefInfo;
|
|
|
|
|
|
// 6. 将线实体添加到drawnEntities中
|
|
|
this.drawnEntities.push(lineEntity);
|
|
|
},
|
|
|
// 添加面到地图中
|
|
|
- addPolygon(coordinates) {
|
|
|
+ addPolygon(coordinates, featureProperties = null, featureRefInfo = null) {
|
|
|
// 检测是否为MultiPolygon类型(MultiPolygon的坐标是三维数组)
|
|
|
if (
|
|
|
Array.isArray(coordinates[0]) &&
|
|
|
@@ -738,15 +1159,15 @@ export default {
|
|
|
console.log("MultiPolygon coordinates", coordinates);
|
|
|
// 是MultiPolygon类型,遍历每个Polygon
|
|
|
coordinates.forEach((polygonCoordinates) => {
|
|
|
- this.renderSinglePolygon(polygonCoordinates);
|
|
|
+ this.renderSinglePolygon(polygonCoordinates, featureProperties, featureRefInfo);
|
|
|
});
|
|
|
} else {
|
|
|
// 是单个Polygon类型
|
|
|
- this.renderSinglePolygon(coordinates);
|
|
|
+ this.renderSinglePolygon(coordinates, featureProperties, featureRefInfo);
|
|
|
}
|
|
|
},
|
|
|
// 渲染单个Polygon
|
|
|
- renderSinglePolygon(coordinates) {
|
|
|
+ renderSinglePolygon(coordinates, featureProperties = null, featureRefInfo = null) {
|
|
|
// 1. 处理坐标格式:确保获取外部环坐标
|
|
|
const outerRingCoordinates =
|
|
|
Array.isArray(coordinates[0]) && Array.isArray(coordinates[0][0])
|
|
|
@@ -811,6 +1232,8 @@ export default {
|
|
|
extrudedMaterial: SkyScenery.Color.GREEN.withAlpha(0.8),
|
|
|
},
|
|
|
});
|
|
|
+ polygonEntity.__featureProperties = featureProperties;
|
|
|
+ polygonEntity.__featureRef = featureRefInfo;
|
|
|
|
|
|
// 5. 将面实体添加到drawnEntities中
|
|
|
this.drawnEntities.push(polygonEntity);
|
|
|
@@ -864,12 +1287,99 @@ export default {
|
|
|
this.clearAll();
|
|
|
this.jsonData = {};
|
|
|
this.backData = {};
|
|
|
+ this.renderStatus = {
|
|
|
+ input: false,
|
|
|
+ output: false,
|
|
|
+ };
|
|
|
+ this.renderedGeojsonCache = {
|
|
|
+ input: null,
|
|
|
+ output: null,
|
|
|
+ };
|
|
|
+ this.currentRenderedSource = "";
|
|
|
+ this.propertyDialog.visible = false;
|
|
|
+ this.propertyDialog.list = [];
|
|
|
+ this.propertyDialog.source = "";
|
|
|
+ this.propertyDialog.propertiesRef = null;
|
|
|
+ this.propertyDialog.featureIndex = -1;
|
|
|
this.SceneValue = emit[emit.length - 1];
|
|
|
},
|
|
|
// 初始化绘制处理器
|
|
|
initDrawHandler() {
|
|
|
// 创建绘制处理器
|
|
|
- this.handler = new SkyScenery.ScreenSpaceEventHandler(viewer.canvas);
|
|
|
+ if (!this.handler) {
|
|
|
+ this.handler = new SkyScenery.ScreenSpaceEventHandler(viewer.canvas);
|
|
|
+ }
|
|
|
+ this.initFeaturePickHandler();
|
|
|
+ },
|
|
|
+ // 初始化地图要素拾取处理器(用于属性弹框)
|
|
|
+ initFeaturePickHandler() {
|
|
|
+ if (this.featurePickHandler || !viewer || !viewer.canvas) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.featurePickHandler = new SkyScenery.ScreenSpaceEventHandler(viewer.canvas);
|
|
|
+ this.featurePickHandler.setInputAction((movement) => {
|
|
|
+ // 绘制模式中不触发属性弹框,避免用户操作冲突
|
|
|
+ if (this.currentTool || this.isDrawingHole) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const pickedObject = viewer.scene.pick(movement.position);
|
|
|
+ if (!SkyScenery.defined(pickedObject) || !SkyScenery.defined(pickedObject.id)) {
|
|
|
+ this.propertyDialog.visible = false;
|
|
|
+ this.propertyDialog.list = [];
|
|
|
+ this.propertyDialog.source = "";
|
|
|
+ this.propertyDialog.propertiesRef = null;
|
|
|
+ this.propertyDialog.featureIndex = -1;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const entity = pickedObject.id;
|
|
|
+ const featureIndex =
|
|
|
+ entity.__featureRef && typeof entity.__featureRef.featureIndex === "number"
|
|
|
+ ? entity.__featureRef.featureIndex
|
|
|
+ : -1;
|
|
|
+ const propertySource =
|
|
|
+ (entity.__featureRef && entity.__featureRef.source) ||
|
|
|
+ this.currentRenderedSource ||
|
|
|
+ "input";
|
|
|
+ const geojsonData =
|
|
|
+ this.getSourceGeojsonData(propertySource) || this.renderedGeojsonCache[propertySource];
|
|
|
+ let propertiesRef = null;
|
|
|
+ if (geojsonData) {
|
|
|
+ if (
|
|
|
+ entity.__featureRef &&
|
|
|
+ typeof entity.__featureRef.featureIndex === "number" &&
|
|
|
+ entity.__featureRef.featureIndex >= 0 &&
|
|
|
+ geojsonData.features &&
|
|
|
+ geojsonData.features[entity.__featureRef.featureIndex]
|
|
|
+ ) {
|
|
|
+ if (!geojsonData.features[entity.__featureRef.featureIndex].properties) {
|
|
|
+ geojsonData.features[entity.__featureRef.featureIndex].properties = {};
|
|
|
+ }
|
|
|
+ propertiesRef = geojsonData.features[entity.__featureRef.featureIndex].properties;
|
|
|
+ } else {
|
|
|
+ if (!geojsonData.properties) {
|
|
|
+ geojsonData.properties = {};
|
|
|
+ }
|
|
|
+ propertiesRef = geojsonData.properties;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const propertyList = this.formatFeatureProperties(
|
|
|
+ propertiesRef || entity.__featureProperties || {}
|
|
|
+ );
|
|
|
+ if (propertiesRef || (entity.__featureProperties && typeof entity.__featureProperties === "object")) {
|
|
|
+ this.propertyDialog.source = propertySource;
|
|
|
+ this.propertyDialog.propertiesRef =
|
|
|
+ propertiesRef || entity.__featureProperties || {};
|
|
|
+ this.propertyDialog.featureIndex = featureIndex;
|
|
|
+ this.propertyDialog.list = propertyList;
|
|
|
+ this.propertyDialog.visible = true;
|
|
|
+ } else {
|
|
|
+ this.propertyDialog.visible = false;
|
|
|
+ this.propertyDialog.list = [];
|
|
|
+ this.propertyDialog.source = "";
|
|
|
+ this.propertyDialog.propertiesRef = null;
|
|
|
+ this.propertyDialog.featureIndex = -1;
|
|
|
+ }
|
|
|
+ }, SkyScenery.ScreenSpaceEventType.LEFT_CLICK);
|
|
|
},
|
|
|
|
|
|
// 激活绘制工具
|
|
|
@@ -1594,6 +2104,12 @@ export default {
|
|
|
// 清空数组
|
|
|
this.drawnEntities = [];
|
|
|
this.geometries = [];
|
|
|
+ this.currentRenderedSource = "";
|
|
|
+ this.propertyDialog.visible = false;
|
|
|
+ this.propertyDialog.list = [];
|
|
|
+ this.propertyDialog.source = "";
|
|
|
+ this.propertyDialog.propertiesRef = null;
|
|
|
+ this.propertyDialog.featureIndex = -1;
|
|
|
// 取消当前绘制模式
|
|
|
this.deactivateDraw();
|
|
|
console.log("已清除所有绘制的元素");
|
|
|
@@ -1679,6 +2195,56 @@ export default {
|
|
|
border-color: rgba(255, 255, 255, 0.4);
|
|
|
}
|
|
|
}
|
|
|
+.feature-property-content {
|
|
|
+ max-height: 360px;
|
|
|
+ overflow: auto;
|
|
|
+ padding-right: 4px;
|
|
|
+}
|
|
|
+.feature-property-tip {
|
|
|
+ color: #9ed2ff;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+.feature-property-item {
|
|
|
+ padding: 10px 12px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: rgba(61, 132, 205, 0.14);
|
|
|
+ border: 1px solid rgba(111, 186, 255, 0.35);
|
|
|
+}
|
|
|
+.feature-property-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+.feature-property-delete-btn {
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.feature-property-key-input {
|
|
|
+ width: 180px;
|
|
|
+}
|
|
|
+:deep(.feature-property-input .el-input__wrapper) {
|
|
|
+ background: rgba(8, 34, 74, 0.68);
|
|
|
+ box-shadow: inset 0 0 0 1px rgba(130, 198, 255, 0.4);
|
|
|
+}
|
|
|
+:deep(.feature-property-input .el-input__inner) {
|
|
|
+ color: #ffffff;
|
|
|
+}
|
|
|
+:deep(.feature-property-dialog .el-dialog) {
|
|
|
+ background: rgba(7, 24, 48, 0.95);
|
|
|
+ border: 1px solid rgba(111, 186, 255, 0.5);
|
|
|
+}
|
|
|
+:deep(.feature-property-dialog .el-dialog__title) {
|
|
|
+ color: #e8f4ff;
|
|
|
+ font-weight: 700;
|
|
|
+}
|
|
|
+:deep(.feature-property-dialog .el-dialog__headerbtn .el-dialog__close) {
|
|
|
+ color: #d8ecff;
|
|
|
+}
|
|
|
+:deep(.feature-property-dialog .el-dialog__body) {
|
|
|
+ padding-top: 12px;
|
|
|
+}
|
|
|
:deep(.ace_editor) {
|
|
|
height: 600px !important;
|
|
|
max-height: calc(100vh - 300px) !important;
|