Openlayers调用ArcGis地图服务之四 —— 要素查找(/find)

0 阅读7分钟

1.4 Openlayers调用ArcGis地图服务之要素查找(/find)

下面使用ArcGis官方服务作为示例直接调用(如果使用自己的私有服务,可能先要获取token)

各个库版本如下:

    "ol": "^10.8.0",
    "proj4": "^2.20.8",
    "vue3-openlayers": "^12.2.2"

目录

1.4 要素查找【地图服务的查询接口(/find)】

1.4.1 介绍

要素查询主要用于对发布的服务中的要素(Features)进行查询和筛选。它允许客户端(Web 应用、桌面软件、脚本)基于关键字来获取数据,使用/find接口可以针对多个图层,不支持空间过滤

1.4.1.1 核心用法

跨图层通过关键字匹配获取要素

1.4.1.2 关键参数
  • searchText:要搜索的文本字符串。

  • contains:是否为模糊匹配(true/false)。

  • searchFields:要搜索的字段列表(逗号分隔)。

  • layers:要搜索的图层ID列表(逗号分隔)。

  • returnGeometry:是否返回几何图形。

1.4.2 判断

在服务信息页面ArcGis官方服务3

35.png

36.png

可以看到Supported Operations中有Find可以判断支持使用find查询要素

1.4.3 调用
1.4.3.1 在线调用

36.png

点击Find

37.png

点击提交后:

38.png

可以看到,跨图层搜索到的features

1.4.3.2 Openlayers调用

39.png

可以看到关键字搜索到点要素和多边形要素都渲染出来

<template>
  <div class="map-page">
    <h1>OpenLayers - ArcGIS 要素查询(find)</h1>
    <div class="controls">
      <input
        v-model="searchText"
        placeholder="输入关键字 (如: edi)"
        @keyup.enter="findArcGIS"
        class="search-input"
      />
      <button @click="findArcGIS" :disabled="loading">
        {{ loading ? "搜索中..." : "搜索" }}
      </button>
      <span v-if="resultCount" class="result-count"
        >找到 {{ resultCount }} 条结果</span
      >
    </div>

    <!-- 结果列表 -->
    <div v-if="results.length > 0" class="results-panel">
      <h3>搜索结果</h3>
      <div class="results-list">
        <div
          v-for="(result, index) in results"
          :key="index"
          class="result-item"
          @click="zoomToResult(result)"
        >
          <div class="result-layer">
            {{ result.layerName }} (Layer {{ result.layerId }})
          </div>
          <div class="result-name">{{ result.value }}</div>
        </div>
      </div>
    </div>

    <div id="ol-find-map" ref="mapContainer" class="map-container"></div>
    <div v-if="error" class="error">{{ error }}</div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from "vue";
import axios from "axios";
import Map from "ol/Map";
import View from "ol/View";
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
import { Style, Circle, Fill, Stroke } from "ol/style";
import Polygon from "ol/geom/Polygon";
import Feature from "ol/Feature";
import Point from "ol/geom/Point";

const mapContainer = ref<HTMLDivElement>();
const searchText = ref("edi");
const loading = ref(false);
const resultCount = ref(0);
const error = ref("");
const results = ref<any[]>([]);

let map: Map | null = null;
const resultLayer = new VectorLayer({
  source: new VectorSource(),
  style: function (feature) {
    const geometryType = feature.get("geometryType");
    if (geometryType === "esriGeometryPolygon") {
      return new Style({
        stroke: new Stroke({
          color: "#FF0000",
          width: 2,
        }),
        fill: new Fill({
          color: "rgba(255, 0, 0, 0.2)",
        }),
      });
    } else {
      return new Style({
        image: new Circle({
          radius: 8,
          fill: new Fill({ color: "rgba(255, 0, 0, 0.5)" }),
          stroke: new Stroke({ color: "#FF0000", width: 2 }),
        }),
      });
    }
  },
});

// Find ArcGIS features
const findArcGIS = async () => {
  if (!searchText.value.trim()) {
    error.value = "请输入搜索关键字";
    return;
  }

  loading.value = true;
  error.value = "";
  results.value = [];
  resultCount.value = 0;

  try {
    // 使用 POST FormData 请求
    const formData = new FormData();
    formData.append("searchText", searchText.value);
    formData.append("contains", "true");
    formData.append("layers", "0,1,2,3");
    formData.append("searchFields", "");
    formData.append("f", "pjson");

    const response = await axios.post(
      "https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer/find",
      formData,
    );

    results.value = response.data.results || [];
    resultCount.value = results.value.length;

    // Clear previous results
    const source = resultLayer.getSource();
    source?.clear();

    // 添加所有结果到地图
    results.value.forEach((result) => {
      if (!result.geometry) return;

      const geometryType = result.geometryType;

      if (
        geometryType === "esriGeometryPoint" &&
        result.geometry.x &&
        result.geometry.y
      ) {
        // 点要素
        const pointFeature = {
          type: "Feature",
          geometry: {
            type: "Point",
            coordinates: [result.geometry.x, result.geometry.y],
          },
          properties: result.attributes,
        };
        // 使用原生 OpenLayers API 创建点要素
        const feature = new Feature({
          geometry: new Point([result.geometry.x, result.geometry.y]),
        });
        feature.set("geometryType", "esriGeometryPoint");
        feature.setProperties(result.attributes || {});
        feature.set("layerName", result.layerName);
        feature.set("layerId", result.layerId);
        source?.addFeature(feature);
      } else if (
        geometryType === "esriGeometryPolygon" &&
        result.geometry.rings
      ) {
        // 多边形要素

        result.geometry.rings.forEach((ring: number[][]) => {
          // ArcGIS 的 rings 坐标需要反转 (x,y) -> (lon, lat)
          const coords = ring.map((coord) => [coord[0], coord[1]]);
          const polygon = new Polygon([coords]);
          polygon.transform("EPSG:4326", "EPSG:4326");

          const feature = new Feature({
            geometry: polygon,
          });
          feature.set("geometryType", "esriGeometryPolygon");
          feature.setProperties(result.attributes || {});
          feature.set("layerName", result.layerName);
          feature.set("layerId", result.layerId);
          source?.addFeature(feature);
        });
      }
    });

    // 缩放到结果范围
    const extent = source?.getExtent();
    const view = map?.getView();
    if (view && extent && !extent.some((v) => v === Infinity)) {
      view.fit(extent, { padding: [50, 50, 50, 50], duration: 1000 });
    }
  } catch (err: any) {
    error.value = "搜索失败: " + (err.message || "未知错误");
    console.error("Find error:", err);
  } finally {
    loading.value = false;
  }
};

// Zoom to specific result
const zoomToResult = (result: any) => {
  if (result.geometry) {
    const view = map?.getView();
    if (view) {
      if (
        result.geometryType === "esriGeometryPoint" &&
        result.geometry.x &&
        result.geometry.y
      ) {
        view.animate({
          center: [result.geometry.x, result.geometry.y],
          zoom: 8,
          duration: 500,
        });
      } else if (
        result.geometryType === "esriGeometryPolygon" &&
        result.geometry.rings
      ) {
        // 计算多边形中心
        const ring = result.geometry.rings[0];
        const xs = ring.map((c) => c[0]);
        const ys = ring.map((c) => c[1]);
        const centerX = (Math.min(...xs) + Math.max(...xs)) / 2;
        const centerY = (Math.min(...ys) + Math.max(...ys)) / 2;
        view.animate({
          center: [centerX, centerY],
          zoom: 8,
          duration: 500,
        });
      }
    }
  }
};

onMounted(() => {
  // Create map
  map = new Map({
    target: mapContainer.value!,
    layers: [resultLayer],
    view: new View({
      projection: "EPSG:4326",
      center: [-98.5795, 39.8283], // USA center (lon, lat)
      zoom: 4,
    }),
  });

  // Auto search on mount
  findArcGIS();
});

onUnmounted(() => {
  if (map) {
    map.setTarget(undefined);
    map = null;
  }
});
</script>

<style scoped>
.map-page {
  padding: 20px;
}

h1 {
  margin-bottom: 20px;
  color: #333;
}

.controls {
  margin-bottom: 15px;
  display: flex;
  align-items: center;
  gap: 15px;
}

.search-input {
  padding: 10px 15px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 14px;
  width: 250px;
}

.search-input:focus {
  outline: none;
  border-color: #42b983;
}

.controls button {
  padding: 10px 20px;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: background-color 0.3s;
}

.controls button:hover:not(:disabled) {
  background-color: #3aa876;
}

.controls button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.result-count {
  color: #666;
  font-size: 14px;
}

.results-panel {
  margin-bottom: 15px;
  padding: 15px;
  background-color: #f9f9f9;
  border-radius: 8px;
}

.results-panel h3 {
  margin: 0 0 10px 0;
  font-size: 16px;
  color: #333;
}

.results-list {
  max-height: 200px;
  overflow-y: auto;
}

.result-item {
  padding: 10px;
  background-color: white;
  border: 1px solid #ddd;
  border-radius: 4px;
  margin-bottom: 8px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.result-item:hover {
  background-color: #f0f0f0;
}

.result-layer {
  font-size: 12px;
  color: #666;
  margin-bottom: 4px;
}

.result-name {
  font-size: 14px;
  font-weight: 500;
  color: #333;
}

.map-container {
  width: 100%;
  height: 600px;
  border: 2px solid #ddd;
  border-radius: 8px;
}

.error {
  margin-top: 10px;
  padding: 10px;
  background-color: #fee;
  color: #c33;
  border-radius: 4px;
}
</style>

1.4.3.3 Vue3-Openlayers调用

40.png

可以看到关键字搜索到点要素和多边形要素都渲染出来

<template>
  <div class="map-page">
    <h1>Vue3-OpenLayers - ArcGIS 要素查询(find)</h1>
    <div class="controls">
      <input
        v-model="searchText"
        placeholder="输入关键字 (如: edi)"
        @keyup.enter="findArcGIS"
        class="search-input"
      />
      <button @click="findArcGIS" :disabled="loading">
        {{ loading ? "搜索中..." : "搜索" }}
      </button>
      <span v-if="resultCount" class="result-count"
        >找到 {{ resultCount }} 条结果</span
      >
    </div>

    <!-- 结果列表 -->
    <div v-if="results.length > 0" class="results-panel">
      <h3>搜索结果</h3>
      <div class="results-list">
        <div
          v-for="(result, index) in results"
          :key="index"
          class="result-item"
          @click="zoomToResult(result)"
        >
          <div class="result-layer">
            {{ result.layerName }} (Layer {{ result.layerId }})
          </div>
          <div class="result-name">{{ result.value }}</div>
        </div>
      </div>
    </div>

    <ol-map
      ref="mapRef"
      :loadTilesWhileAnimating="true"
      :loadTilesWhileInteracting="true"
      style="
        height: 600px;
        width: 100%;
        border: 2px solid #ddd;
        border-radius: 8px;
      "
    >
      <ol-view
        ref="viewRef"
        :center="center"
        :zoom="4"
        :projection="projection"
      />

      <!-- Base map layer (commented out) -->
      <!-- <ol-tile-layer>
        <ol-source-xyz
          url="https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer/tile/{z}/{y}/{x}"
          attributions="ArcGIS"
          :projection="projection"
          :tileGrid="tileGrid"
        />
      </ol-tile-layer> -->

      <ol-vector-layer>
        <ol-source-vector ref="vectorSourceRef">
          <ol-style>
            <ol-style-circle :radius="8">
              <ol-style-fill color="rgba(255, 0, 0, 0.5)" />
              <ol-style-stroke color="#FF0000" :width="2" />
            </ol-style-circle>
            <ol-style-stroke color="#FF0000" :width="2" />
            <ol-style-fill color="rgba(255, 0, 0, 0.2)" />
          </ol-style>
        </ol-source-vector>
      </ol-vector-layer>
    </ol-map>

    <div v-if="error" class="error">{{ error }}</div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from "vue";
import axios from "axios";
import Point from "ol/geom/Point";
import Polygon from "ol/geom/Polygon";
import Feature from "ol/Feature";

const projection = "EPSG:4326";
const center = ref([-98.5795, 39.8283]); // USA center (lon, lat)
const searchText = ref("edi");
const loading = ref(false);
const resultCount = ref(0);
const error = ref("");
const results = ref<any[]>([]);
const vectorSourceRef = ref();
const viewRef = ref();
const mapRef = ref();

// Find ArcGIS features
const findArcGIS = async () => {
  if (!searchText.value.trim()) {
    error.value = "请输入搜索关键字";
    return;
  }

  loading.value = true;
  error.value = "";
  results.value = [];
  resultCount.value = 0;

  try {
    // 使用 POST FormData 请求
    const formData = new FormData();
    formData.append("searchText", searchText.value);
    formData.append("contains", "true");
    formData.append("layers", "0,1,2,3");
    formData.append("searchFields", "");
    formData.append("f", "pjson");

    const response = await axios.post(
      "https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer/find",
      formData,
    );

    results.value = response.data.results || [];
    resultCount.value = results.value.length;

    // Clear previous results
    const source = vectorSourceRef.value?.source;
    if (source) {
      source.clear();
    }

    // 添加所有结果到地图
    const features: Feature[] = [];

    results.value.forEach((result) => {
      if (!result.geometry) return;

      const geometryType = result.geometryType;

      if (
        geometryType === "esriGeometryPoint" &&
        result.geometry.x &&
        result.geometry.y
      ) {
        // 点要素
        const pointFeature = new Feature({
          geometry: new Point([result.geometry.x, result.geometry.y]),
        });
        pointFeature.set("geometryType", "esriGeometryPoint");
        pointFeature.setProperties(result.attributes || {});
        pointFeature.set("layerName", result.layerName);
        pointFeature.set("layerId", result.layerId);
        features.push(pointFeature);
      } else if (
        geometryType === "esriGeometryPolygon" &&
        result.geometry.rings
      ) {
        // 多边形要素
        result.geometry.rings.forEach((ring: number[][]) => {
          const coords = ring.map((coord) => [coord[0], coord[1]]);
          const polygonFeature = new Feature({
            geometry: new Polygon([coords]),
          });
          polygonFeature.set("geometryType", "esriGeometryPolygon");
          polygonFeature.setProperties(result.attributes || {});
          polygonFeature.set("layerName", result.layerName);
          polygonFeature.set("layerId", result.layerId);
          features.push(polygonFeature);
        });
      }
    });

    if (source) {
      source.addFeatures(features);

      // Zoom to results
      if (features.length > 0) {
        const extent = source.getExtent();
        const view = viewRef.value?.view;
        if (view && extent && !extent.some((v: number) => v === Infinity)) {
          view.fit(extent, { padding: [50, 50, 50, 50], duration: 1000 });
        }
      }
    }
  } catch (err: any) {
    error.value = "搜索失败: " + (err.message || "未知错误");
    console.error("Find error:", err);
  } finally {
    loading.value = false;
  }
};

// Zoom to specific result
const zoomToResult = (result: any) => {
  if (result.geometry) {
    const view = viewRef.value?.view;
    if (view) {
      if (
        result.geometryType === "esriGeometryPoint" &&
        result.geometry.x &&
        result.geometry.y
      ) {
        view.animate({
          center: [result.geometry.x, result.geometry.y],
          zoom: 8,
          duration: 500,
        });
      } else if (
        result.geometryType === "esriGeometryPolygon" &&
        result.geometry.rings
      ) {
        // 计算多边形中心
        const ring = result.geometry.rings[0];
        const xs = ring.map((c: number[]) => c[0]);
        const ys = ring.map((c: number[]) => c[1]);
        const centerX = (Math.min(...xs) + Math.max(...xs)) / 2;
        const centerY = (Math.min(...ys) + Math.max(...ys)) / 2;
        view.animate({
          center: [centerX, centerY],
          zoom: 8,
          duration: 500,
        });
      }
    }
  }
};

onMounted(() => {
  // Auto search on mount
  findArcGIS();
});
</script>

<style scoped>
.map-page {
  padding: 20px;
}

h1 {
  margin-bottom: 20px;
  color: #333;
}

.controls {
  margin-bottom: 15px;
  display: flex;
  align-items: center;
  gap: 15px;
}

.search-input {
  padding: 10px 15px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 14px;
  width: 250px;
}

.search-input:focus {
  outline: none;
  border-color: #42b983;
}

.controls button {
  padding: 10px 20px;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: background-color 0.3s;
}

.controls button:hover:not(:disabled) {
  background-color: #3aa876;
}

.controls button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.result-count {
  color: #666;
  font-size: 14px;
}

.results-panel {
  margin-bottom: 15px;
  padding: 15px;
  background-color: #f9f9f9;
  border-radius: 8px;
}

.results-panel h3 {
  margin: 0 0 10px 0;
  font-size: 16px;
  color: #333;
}

.results-list {
  max-height: 200px;
  overflow-y: auto;
}

.result-item {
  padding: 10px;
  background-color: white;
  border: 1px solid #ddd;
  border-radius: 4px;
  margin-bottom: 8px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.result-item:hover {
  background-color: #f0f0f0;
}

.result-layer {
  font-size: 12px;
  color: #666;
  margin-bottom: 4px;
}

.result-name {
  font-size: 14px;
  font-weight: 500;
  color: #333;
}

.error {
  margin-top: 10px;
  padding: 10px;
  background-color: #fee;
  color: #c33;
  border-radius: 4px;
}
</style>