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
可以看到Supported Operations中有Find可以判断支持使用find查询要素
1.4.3 调用
1.4.3.1 在线调用
点击Find
点击提交后:
可以看到,跨图层搜索到的features
1.4.3.2 Openlayers调用
可以看到关键字搜索到点要素和多边形要素都渲染出来
<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调用
可以看到关键字搜索到点要素和多边形要素都渲染出来
<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>