做 GIS 数据对接和地图页面开发的这几年,我养成了一个习惯:每隔一段时间就把项目里用到的 ArcGIS REST API 接口重新拉一遍,核对服务地址、图层 ID、坐标系、字段名和 Token 有效期。因为这套接口不像 SDK 那样给你一堆封装好的类,它把服务能力摊开成一个个 HTTP 地址,服务目录、图层查询、要素编辑、空间分析,全靠 URL 参数来驱动。本文就是一份 ArcGIS REST API 的实践记录,把我经常用、也容易踩坑的端点、参数和排查方法整理在一起。如果你在做数据接入、自动化报表、Web 地图前端,或者需要把 GIS 服务能力集成到业务系统里,这页笔记应该能节省不少试错时间。很多时候网上教程只告诉你“怎么调通一次”,却没人说“拿到地址之后该记哪些信息”。所以我这篇不是复制官方文档,而是站在实操角度,把接口从“能通”讲到“能长期用”。1. 先搞清 ArcGIS REST API 到底在什么位置刚接触 ArcGIS REST API 的人,最容易犯的错是把重点放在“怎么写代码”上,结果连服务目录结构都没看明白。其实这套接口的规律性很强,只要理解了一次访问过程,后面各种服务都能举一反三。1.1 一个 URL 就是一类能力ArcGIS Server 或 Portal 发布服务后,所有能力都会按照固定路径暴露出来。顶层是服务目录,下面挂地图服务、要素服务、影像服务、地理处理服务等类型。通常一个地址长这样:https://your-server/arcgis/rest/services/城市基础/MapServer把最后一段从MapServer换成FeatureServer,可能就是同一个数据源的可编辑版本;换成GPServer,则可能是一个工具服务。记住这个层级关系,就能做到看到 URL 就大致猜出服务类型。ArcGIS REST API 的每个操作也基本是“服务地址 + 操作名 + 参数”。比如查询图层,地址是:https://your-server/arcgis/rest/services/城市基础/MapServer/3/query后面的query就是操作名,前面/3/是图层 ID。参数里带where=1=1、outFields=*、returnGeometry=true、f=json之类。整个接口没有复杂的 SDK 概念,核心就是 URL 和参数。注意:这条路径中的端口号、站点名不一定固定。很多单位部署时会自己在前面套一层网关或反向代理,实际访问地址和服务器内部地址可能不一样。记录接口时,先把“业务系统实际访问的地址”和“服务器管理员给的原始地址”分开记。1.2 记录时要先看目录,而不是猜参数拿到一个新服务地址,我的习惯是先打开服务目录看一眼,而不是直接拼一个查询参数去试。在浏览器访问服务地址末尾加?f=json,就能看到这段 JSON 里包含的关键信息:{ "serviceDescription": "城市基础数据", "maxRecordCount": 2000, "supportedQueryFormats": "JSON, geoJSON, PBF", "capabilities": "Map,Query,Data", "layers": [] }这段 JSON 比任何文档都诚实。它告诉你这个服务能做什么、最多返回多少条记录、支持哪种格式。比如supportedQueryFormats里写了geoJSON,前端就可以直接用f=geojson;没写的话,老老实实继续用f=json,别盲目依赖第三方库的自动转换。目录里的layers数组还包含了每个图层的 ID、名称、几何类型、字段列表、空间参考信息。我建议每次打通接口后,把这个目录 JSON 存一份在本地,作为后续开发、联调、排查的基线。1.3 打开“?f=json”时到底看哪些字段同样一个?f=json,新手可能只看有没有 layers,老手会多看几个字段。重点信息可以整理成一张清单:字段含义为什么重要maxRecordCount单次查询最大返回条数决定分页逻辑怎么写capabilities服务能力集合判断只读还是可编辑supportedQueryFormats支持响应的数据格式决定能不能用 GeoJSONspatialReference服务原生坐标系坐标转换的基础fullExtent数据全集范围空间过滤边界参考initialExtent初始化显示范围前端初始化地图时会用到其中capabilities最容易忽略。有的服务看起来是 FeatureServer,但管理端只开了Query没开Editing,代码里调用增删改接口就会一直报 403 或“操作不可用”。提前看目录,能省掉大量摸黑排查时间。2. 端点选型:MapServer 和 FeatureServer 别用混很多文章把 ArcGIS REST API 笼统说成“一个 URL 就完事”,但真要接入业务系统时,选错服务类型会带来两种完全不同的结果:一种是你调了半天写不进去,另一种是数据读得太慢。所以先把服务类型分清楚。2.1 MapServer 适合只读和地图展示MapServer 是原生的“地图服务”,最擅长的是把地理数据组织成图层,然后按照空间范围、比例尺、图例渲染出来。常见操作包括:export:按范围、大小、图层透明度输出一张地图图片identify:根据屏幕坐标或地理坐标,点击识别图层属性find:跨图层搜索某个字段值,返回匹配要素legend:获取图层的符号图例query:按属性和空间条件查询要素举个例子,如果前端要做“点击图层查看详情”,最适合的不是把整张表数据拉到前端再自己算,而是直接调用 MapServer 的identify操作:https://your-server/arcgis/rest/services/城市基础/MapServer/identify ?geometry=-122.2,37.5 &geometryType=esriGeometryPoint &mapExtent=-122.3,37.4,-122.1,37.6 &layers=all &tolerance=5 &f=json这个请求会让服务端根据当前地图范围、点击位置和容差,把范围内的要素属性返回给你。效率很高,也避免了把大量数据推到前端。但 MapServer 的query只能读,不能写。如果想在前端交互中让用户新增、修改、删除要素,MapServer 做不到,需要切到 FeatureServer。提示:identify的mapExtent是必需参数,很多人漏了它,导致结果为空或报错。如果你只是做纯属性查询,用query就可以了,不需要走identify。2.2 FeatureServer 才是能写数据的地方FeatureServer 是要素服务,本质上是在 MapServer 基础上叠加了编辑能力。它最常见的操作有:query:查询要素,返回几何和属性addFeatures:新增要素updateFeatures:更新要素deleteFeatures:删除要素applyEdits:在一个请求里混合新增、更新、删除applyEdits是项目里最常用的。它支持 JSON 格式的 features 数组,比如新增一个点要素:POST https://your-server/arcgis/rest/services/城市基础/FeatureServer/0/applyEdits请求体大致是:{ "adds": [ { "attributes": { "NAME": "测试点", "CODE": "A001" }, "geometry": { "x": 120.1, "y": 30.2, "spatialReference": { "wkid": 4326 } } } ], "f": "json" }这里要特别注意:objectId字段通常由服务端自动生成,新增时不要传,否则可能报唯一值冲突;更新时必须传对象 ID,否则服务端不知道你要改哪条记录。这些细节在目录 JSON 的字段定义里都能看到。判断一个 FeatureServer 是否能写,最直接的办法是看目录 JSON 的capabilities字段。里面包含Create、Update、Delete、Editing等关键字,才说明它开了编辑权限。只看到Query的话,就算调用applyEdits也只会得到错误。2.3 ImageServer 和其他服务类型也顺手记一下除了 MapServer 和 FeatureServer,实际项目里还会遇到 ImageServer、GPServer。ImageServer 主要面向影像数据,例如卫星影像、栅格数据,常见操作是exportImage和getSamples,返回的是影像或采样值,不是矢量要素。GPServer 则是地理处理服务,它把模型或脚本发布成工具,调用时用submitJob提交任务、jobResults查询结果。这些服务日常用得没前两类频繁,但记录接口时一定要在文档里标注服务类型。因为不同服务类型对应的操作名完全不一样,同一段代码如果只改了端点后缀,很可能直接报“找不到操作”。3. 实操:从拿 Token 到完成一次空间查询接下来进入正题:一个完整的 ArcGIS REST API 调用过程,到底要经历哪些环节。我以一个常见场景为例:登录服务、查询某个范围内的设施点、把结果字段取出来用于业务展示。3.1 先拿 Token,别把密码写在请求里ArcGIS Server 的接口是否需要 Token,取决于服务是否公开。很多单位的服务都是内网加密的,调用必须带 Token。Token 的获取方式不同版本稍有差别,Portal 环境常用:POST https://your-server/arcgis/sharing/rest/generateToken请求参数:username=your_username password=your_password referer=https://your-app-server f=json返回 JSON 里会有token字段,例如:{ "token": "abc123...", "expires": 1700000000000, "ssl": true }拿到 Token 后,后续请求可以放在 URL 的token参数里,也可以放在请求头X-Esri-Authorization: Bearer <token>里。放在 URL 里调试时很直观,但如果 Token 出现在访问日志里,会泄露权限信息,所以正式生产环境建议用请求头。注意:Token 有时效性,短则几十分钟、长则数小时,具体看服务端配置。脚本里不要写死 Token,每次运行前重新获取,或者先判断expires时间再决定是否刷新。我见过不少线上故障都是因为凌晨定时任务跑的时候 Token 恰好过期,任务失败后又没有人看日志。3.2 Query 参数逐个拆解:where、outFields、returnGeometry查询操作是所有接口中使用频率最高的,但很多人喜欢直接复制一个“万能模板”,却不知道每个参数为什么这样写。我的习惯是逐个拆解:参数示例作用where1=1SQL 风格的过滤条件outFieldsNAME,CODE只返回需要的字段returnGeometrytrue是否返回几何信息outSR4326输出坐标系fjson返回格式举个最小示例:https://your-server/arcgis/rest/services/城市基础/MapServer/3/query ?where=1=1 &outFields=NAME,CODE &returnGeometry=true &outSR=4326 &f=jsonwhere=1=1是查询全部数据的经典写法。因为where必须是合法 SQL 表达式,1=1永远为真,所以等价于不设过滤。但它容易让前端把全量数据拉下来,正式环境一定要加上更严格的条件,比如按区域、状态字段过滤。outFields尽量只写需要的字段。有的图层有几十个字段,如果写*,返回的 JSON 会非常庞大。尤其在地图拖动、异步加载场景下,响应体积直接影响页面性能。returnGeometry=true表示返回每条要素的空间坐标。如果只是做属性校验或者统计数量,改成false,响应量会小很多。3.3 空间查询的参数组合:geometry、spatialRel、inSR纯属性查询比较简单,一旦涉及空间查询,参数就多了。ArcGIS REST API 里最常用的空间查询组合是geometry+geometryType+spatialRel+inSR。例如我在地图上画了一个矩形框,想查这个矩形框内所有设施点,请求是这样的:https://your-server/arcgis/rest/services/城市基础/MapServer/3/query ?where=1=1 &geometry=-122.2,37.4,-122.0,37.6 &geometryType=esriGeometryEnvelope &spatialRel=esriSpatialRelIntersects &inSR=4326 &outSR=4326 &outFields=NAME,CODE &f=json这里geometry的四组数字表示矩形框的xmin,ymin,xmax,ymax,geometryType=esriGeometryEnvelope告诉服务端这是一个外包矩形,inSR=4326声明这个矩形的坐标系,spatialRel=esriSpatialRelIntersects表示只要要素与矩形有交集就返回。空间关系还可以换成esriSpatialRelContains、esriSpatialRelWithin、esriSpatialRelTouches等。我的经验是:Intersects最常用,但要做严格的“完全包含”关系时,必须用Contains或Within,否则边界重叠的记录会被误判。注意:坐标顺序不能写反。写成ymin,xmin,ymax,xmax会返回空结果,因为服务端判断不了这个范围。排查空间查询无数据时,第一件事就是检查坐标顺序和inSR是否与前端坐标系一致。3.4 用 POST 做大量参数请求浏览器地址栏里能用 GET 调通的接口,在生产环境不一定适合。因为where复杂时中文、括号、单引号都要做 URL 编码,地址会变得很长,还容易被网关截断。我的建议是:凡是参数多的查询,统一用 POST 提交表单数据。Python 里用requests写起来很直观:import requests url = "https://your-server/arcgis/rest/services/城市基础/FeatureServer/0/query" params = { "where": "STATUS = '1' AND NAME LIKE '%测试%'", "outFields": "NAME,CODE,STATUS", "returnGeometry": "false", "outSR": "4326", "f": "json", "token": token, } resp = requests.post(url, data=params, timeout=30) data = resp.json() for feature in data.get("features", []): attrs = feature["attributes"] print(attrs["NAME"], attrs["CODE"])POST 方式对参数编码更友好,也方便统一管理超时、代理、重试逻辑。我在自动化脚本里基本都用 POST,只有临时调试才用 GET。4. 把接口记录变成能长期维护的文档前面讲的都是“怎么调通”,但在真实项目里,接口记录的价值在于“可维护”。服务更新、图层结构调整、坐标系变更,都会让之前的代码突然崩掉。所以我建议把 ArcGIS REST API 信息做成一张标准清单,而不是散落在聊天记录里。4.1 需要记录的元信息清单每接入一个服务,我至少记录以下内容:类别具体内容访问地址实际业务地址、原始服务地址服务类型MapServer / FeatureServer / ImageServer / GPServer图层编号同一个服务下多个图层各自的 ID图层名称图层显示名称,有些中文名称会重复几何类型点、线、面、多点原生坐标系服务数据源的 SR,比如3857、4326、4490关键字段业务主键、名称字段、状态字段权限状态是否需要 Token、编辑能力是否开启最大记录数maxRecordCount值更新时间服务最近一次验证的日期这些信息不需要手工复制,直接从服务目录 JSON 里读取就行。我经常做完一次服务盘点后,把结果存成 Markdown 表格,再附一份原始 JSON 作为附件,项目验收、交接新人时都很省事。4.2 用脚本自动盘点图层如果服务数量很少,手工记录没问题;但如果一个系统下挂了几十个服务、上百个图层,手工就不现实了。我会写一个递归脚本,从根目录开始遍历所有 services 和 folders,自动打印出每个服务的图层清单。import requests import json base_url = "https://your-server/arcgis/rest/services" token = "your_token" def list_layers(service_url): resp = requests.get(service_url, params={"f": "json", "token": token}) data = resp.json() for layer in data.get("layers", []): print(layer["id"], layer["name"], layer.get("geometryType")) for table in data.get("tables", []): print("table", table["id"], table["name"]) def walk_folders(folder_path="/"): url = base_url + folder_path resp = requests.get(url, params={"f": "json", "token": token}) data = resp.json() for service in data.get("services", []): print(service["name"], service["type"]) if service["type"] == "MapServer": list_layers(base_url + "/" + service["name"] + "/MapServer") elif service["type"] == "FeatureServer": list_layers(base_url + "/" + service["name"] + "/FeatureServer") for folder in data.get("folders", []): walk_folders(folder_path + "/" + folder)这个脚本可以做成定时巡检任务,每周跑一次,比较前后两次的图层列表。如果某个图层被删了、字段改了,脚本能最早发现,而不是等业务系统报错才知道。4.3 记录字段别名和编码域字段名通常是英文,别名才是业务人员认识的中文名。比如字段叫STATUS,别名是“状态”,值域里可能定义了0代表“停用”、1代表“在用”。写接口记录时,要把别名和值域也存下来。{ "name": "STATUS", "alias": "状态", "type": "esriFieldTypeString", "domain": { "type": "codedValue", "codedValues": [ { "name": "停用", "code": 0 }, { "name": "在用", "code": 1 } ] } }在前端展示时,我通常用别名做表头;写入数据时,再通过编码域把界面文字转成对应的数字或代码。这个转换逻辑如果不记录,后面接手的同事看到STATUS=0会完全不知道是什么意思。5. 常见问题与排查技巧实录ArcGIS REST API 的报错信息不像普通 Web 接口那么友好,很多提示是“Unable to complete operation”这种笼统描述。但大部分坑其实有固定套路,掌握之后能很快定位。5.1 error 400、498、499 代表什么HTTP 状态码是排查接口问题的第一现场。400 Bad Request:参数错误。通常是漏了必填参数、参数类型不对、where 语句语法有问题。做法是检查 URL 里的f=json、geometryType、outSR是否齐全。498 Invalid Token:Token 非法或已过期。重新生成 Token 再看。499 Token Required:请求需要 Token 但没传。确认是否在 URL 或请求头带了 Token,Token 是否被网关过滤。500 Internal Server Error:服务端处理失败,常见原因是数据源连接异常或查询条件太复杂。此时要看服务端日志,客户端往往拿不到详细堆栈。我见过一个很典型的 498 场景:脚本里用requests.get,默认把参数编码到 URL,但 Token 本身包含+/等特殊字符,在某些网关里会被解码改变。解决方法是把 Token 放到 Header 里,或者改用 POST 提交。5.2 中文查询结果为空中文条件过滤是高频问题。比如where=NAME LIKE '%北京%',如果直接在浏览器地址栏粘贴,中文空格会被浏览器部分转义,但送到服务端时编码不对,结果就是返回 0 条但也不报错。我的经验是,中文条件查询尽量用 POST,让 HTTP 库自动处理 UTF-8 编码。如果只能用 GET,那也要先对where做 URL 编码,再用requests或 Postman 调试。另外,where里的字符串值要用英文单引号包裹,例如:where=NAME LIKE '%北京%'这里的单引号不是中文引号,写错会导致 SQL 拼接错误。提示:如果返回结果为空,先在服务目录里手动执行一次f=json,确认数据源是否有数据。很多时候不是查询参数问题,而是这个图层本身就没有记录,或者图层被设置了权限过滤。5.3 返回要素太多,接口很慢ArcGIS REST API 的maxRecordCount限制了单次最多返回多少条,但即使只有 2000 条,如果每次带着完整几何和几十个字段返回,也会很慢。排查慢接口时,我会先做三件事:加returnCountOnly=true看看总数到底有多少,判断是不是全表查询。加空间过滤或where条件缩小范围。outFields只保留业务需要的字段,减少序列化体积。如果要分批拉取大量数据,分页参数是resultOffset和resultRecordCount。示例:https://your-server/arcgis/rest/services/城市基础/FeatureServer/0/query ?where=1=1 &resultOffset=0 &resultRecordCount=500 &outFields=OBJECTID,NAME &returnGeometry=false &f=json下一页就把resultOffset变成 500、1000。不过分页拉取时要注意数据是否在查询过程中被修改,否则会出现跳条或重复。对于只读分析场景,我更推荐用where=OBJECTID > 上次最大值的方式滚动读取,稳定性更好。5.4 坐标对不上,地图位置偏移地图位置偏移通常不是接口的问题,而是坐标系没对齐。服务源数据可能是3857,前端地图用4326,如果请求里没有outSR=4326,返回的就是以服务原始坐标系表达的点,前端叠加到另一套坐标系上自然错位。正确的做法是:查询接口统一加outSR,让它输出前端需要的坐标系空间查询输入时声明inSR,告诉服务端你传入的坐标是什么坐标系不要在浏览器前端再做一次神秘的“坐标偏移”魔法,容易越改越乱如果服务坐标是 CGCS2000 等国内常用坐标系,需要注意服务端版本是否支持对应的 WKID。有的老版本服务不识别某些新 WKID,会直接报错或返回空。这时要么换用同椭球下的近似坐标系,要么在服务端做一次数据源转换,两种情况都要在接口记录里写清楚“实测可用坐标系”。6. 一点个人经验和小工具记录做久了,多少会总结出一些顺手的小工具和习惯。放在最后,纯粹是因为这些东西不算必须的官方知识,但确实能提高效率。6.1 浏览器是最好用的接口调试器调试 ArcGIS REST API,我经常直接打开浏览器,在地址栏里访问服务地址,然后加?f=json。浏览器会把返回的 JSON 自动格式化,缩进、层级一目了然。Chrome 或 Edge 还支持直接在地址栏搜索 JSON 里的字段,比 Postman 还要快。需要把请求串起来组合查询时,我会先用浏览器的“开发者工具”里的网络面板,找到已经成功的查询请求,右键复制为fetch或cURL,再放进脚本里改参数。这样能保证请求头、编码方式和服务端实际接收到的请求完全一致。6.2 自动化巡检比被动维护省心我上面提到过定期跑目录遍历脚本,这里再补充一个实际用法:把脚本放到定时任务里,每天生成一份 JSON 快照。哪天有第三方系统反馈字段不对、图层丢失,我直接把昨天的快照和今天的快照做对比,几秒钟就能定位变更点。这个习惯成本很低,收益却很高。GIS 服务有时会被管理员在后台调整,例如给图层加一个字段、关掉一个能力,这些调整不会主动通知业务方。只有通过巡检才能提前发现问题。6.3 关于“记录”这件事,我的体会是它要一直更新我最早也以为 ArcGIS REST API 记一次就够了,后来发现不同服务器版本、不同数据源、不同服务配置,接口参数细节都会有一点差异。比如同样一个where条件,在某套环境里支持中文别名查询,在另一套环境里只支持字段英文名查询;同样一个空间查询,一个服务用Intersects能返回边界点,另一个服务可能需要Touches。所以我现在每接通一个服务,都会在接口记录末尾写上一行“实测日期、实测环境、可用参数组合”。这不是文档洁癖,而是知道这些信息在使用时才最可靠。希望这份 ArcGIS REST API 记录也能帮你少走几次弯路。
做 GIS 数据对接和地图页面开发的这几年,我养成了一个习惯:每隔一段时间就把项目里用到的 ArcGIS REST API 接口重新拉一遍,核对服务地址、图层 ID、坐标系、字段名和 Token 有效期。因为这套接口不像 SDK 那样给你一堆封装好的类,它把服务能力摊开成一个个 HTTP 地址,服务目录、图层查询、要素编辑、空间分析,全靠 URL 参数来驱动。本文就是一份 ArcGIS REST API 的实践记录,把我经常用、也容易踩坑的端点、参数和排查方法整理在一起。如果你在做数据接入、自动化报表、Web 地图前端,或者需要把 GIS 服务能力集成到业务系统里,这页笔记应该能节省不少试错时间。很多时候网上教程只告诉你“怎么调通一次”,却没人说“拿到地址之后该记哪些信息”。所以我这篇不是复制官方文档,而是站在实操角度,把接口从“能通”讲到“能长期用”。1. 先搞清 ArcGIS REST API 到底在什么位置刚接触 ArcGIS REST API 的人,最容易犯的错是把重点放在“怎么写代码”上,结果连服务目录结构都没看明白。其实这套接口的规律性很强,只要理解了一次访问过程,后面各种服务都能举一反三。1.1 一个 URL 就是一类能力ArcGIS Server 或 Portal 发布服务后,所有能力都会按照固定路径暴露出来。顶层是服务目录,下面挂地图服务、要素服务、影像服务、地理处理服务等类型。通常一个地址长这样:https://your-server/arcgis/rest/services/城市基础/MapServer把最后一段从MapServer换成FeatureServer,可能就是同一个数据源的可编辑版本;换成GPServer,则可能是一个工具服务。记住这个层级关系,就能做到看到 URL 就大致猜出服务类型。ArcGIS REST API 的每个操作也基本是“服务地址 + 操作名 + 参数”。比如查询图层,地址是:https://your-server/arcgis/rest/services/城市基础/MapServer/3/query后面的query就是操作名,前面/3/是图层 ID。参数里带where=1=1、outFields=*、returnGeometry=true、f=json之类。整个接口没有复杂的 SDK 概念,核心就是 URL 和参数。注意:这条路径中的端口号、站点名不一定固定。很多单位部署时会自己在前面套一层网关或反向代理,实际访问地址和服务器内部地址可能不一样。记录接口时,先把“业务系统实际访问的地址”和“服务器管理员给的原始地址”分开记。1.2 记录时要先看目录,而不是猜参数拿到一个新服务地址,我的习惯是先打开服务目录看一眼,而不是直接拼一个查询参数去试。在浏览器访问服务地址末尾加?f=json,就能看到这段 JSON 里包含的关键信息:{ "serviceDescription": "城市基础数据", "maxRecordCount": 2000, "supportedQueryFormats": "JSON, geoJSON, PBF", "capabilities": "Map,Query,Data", "layers": [] }这段 JSON 比任何文档都诚实。它告诉你这个服务能做什么、最多返回多少条记录、支持哪种格式。比如supportedQueryFormats里写了geoJSON,前端就可以直接用f=geojson;没写的话,老老实实继续用f=json,别盲目依赖第三方库的自动转换。目录里的layers数组还包含了每个图层的 ID、名称、几何类型、字段列表、空间参考信息。我建议每次打通接口后,把这个目录 JSON 存一份在本地,作为后续开发、联调、排查的基线。1.3 打开“?f=json”时到底看哪些字段同样一个?f=json,新手可能只看有没有 layers,老手会多看几个字段。重点信息可以整理成一张清单:字段含义为什么重要maxRecordCount单次查询最大返回条数决定分页逻辑怎么写capabilities服务能力集合判断只读还是可编辑supportedQueryFormats支持响应的数据格式决定能不能用 GeoJSONspatialReference服务原生坐标系坐标转换的基础fullExtent数据全集范围空间过滤边界参考initialExtent初始化显示范围前端初始化地图时会用到其中capabilities最容易忽略。有的服务看起来是 FeatureServer,但管理端只开了Query没开Editing,代码里调用增删改接口就会一直报 403 或“操作不可用”。提前看目录,能省掉大量摸黑排查时间。2. 端点选型:MapServer 和 FeatureServer 别用混很多文章把 ArcGIS REST API 笼统说成“一个 URL 就完事”,但真要接入业务系统时,选错服务类型会带来两种完全不同的结果:一种是你调了半天写不进去,另一种是数据读得太慢。所以先把服务类型分清楚。2.1 MapServer 适合只读和地图展示MapServer 是原生的“地图服务”,最擅长的是把地理数据组织成图层,然后按照空间范围、比例尺、图例渲染出来。常见操作包括:export:按范围、大小、图层透明度输出一张地图图片identify:根据屏幕坐标或地理坐标,点击识别图层属性find:跨图层搜索某个字段值,返回匹配要素legend:获取图层的符号图例query:按属性和空间条件查询要素举个例子,如果前端要做“点击图层查看详情”,最适合的不是把整张表数据拉到前端再自己算,而是直接调用 MapServer 的identify操作:https://your-server/arcgis/rest/services/城市基础/MapServer/identify ?geometry=-122.2,37.5 &geometryType=esriGeometryPoint &mapExtent=-122.3,37.4,-122.1,37.6 &layers=all &tolerance=5 &f=json这个请求会让服务端根据当前地图范围、点击位置和容差,把范围内的要素属性返回给你。效率很高,也避免了把大量数据推到前端。但 MapServer 的query只能读,不能写。如果想在前端交互中让用户新增、修改、删除要素,MapServer 做不到,需要切到 FeatureServer。提示:identify的mapExtent是必需参数,很多人漏了它,导致结果为空或报错。如果你只是做纯属性查询,用query就可以了,不需要走identify。2.2 FeatureServer 才是能写数据的地方FeatureServer 是要素服务,本质上是在 MapServer 基础上叠加了编辑能力。它最常见的操作有:query:查询要素,返回几何和属性addFeatures:新增要素updateFeatures:更新要素deleteFeatures:删除要素applyEdits:在一个请求里混合新增、更新、删除applyEdits是项目里最常用的。它支持 JSON 格式的 features 数组,比如新增一个点要素:POST https://your-server/arcgis/rest/services/城市基础/FeatureServer/0/applyEdits请求体大致是:{ "adds": [ { "attributes": { "NAME": "测试点", "CODE": "A001" }, "geometry": { "x": 120.1, "y": 30.2, "spatialReference": { "wkid": 4326 } } } ], "f": "json" }这里要特别注意:objectId字段通常由服务端自动生成,新增时不要传,否则可能报唯一值冲突;更新时必须传对象 ID,否则服务端不知道你要改哪条记录。这些细节在目录 JSON 的字段定义里都能看到。判断一个 FeatureServer 是否能写,最直接的办法是看目录 JSON 的capabilities字段。里面包含Create、Update、Delete、Editing等关键字,才说明它开了编辑权限。只看到Query的话,就算调用applyEdits也只会得到错误。2.3 ImageServer 和其他服务类型也顺手记一下除了 MapServer 和 FeatureServer,实际项目里还会遇到 ImageServer、GPServer。ImageServer 主要面向影像数据,例如卫星影像、栅格数据,常见操作是exportImage和getSamples,返回的是影像或采样值,不是矢量要素。GPServer 则是地理处理服务,它把模型或脚本发布成工具,调用时用submitJob提交任务、jobResults查询结果。这些服务日常用得没前两类频繁,但记录接口时一定要在文档里标注服务类型。因为不同服务类型对应的操作名完全不一样,同一段代码如果只改了端点后缀,很可能直接报“找不到操作”。3. 实操:从拿 Token 到完成一次空间查询接下来进入正题:一个完整的 ArcGIS REST API 调用过程,到底要经历哪些环节。我以一个常见场景为例:登录服务、查询某个范围内的设施点、把结果字段取出来用于业务展示。3.1 先拿 Token,别把密码写在请求里ArcGIS Server 的接口是否需要 Token,取决于服务是否公开。很多单位的服务都是内网加密的,调用必须带 Token。Token 的获取方式不同版本稍有差别,Portal 环境常用:POST https://your-server/arcgis/sharing/rest/generateToken请求参数:username=your_username password=your_password referer=https://your-app-server f=json返回 JSON 里会有token字段,例如:{ "token": "abc123...", "expires": 1700000000000, "ssl": true }拿到 Token 后,后续请求可以放在 URL 的token参数里,也可以放在请求头X-Esri-Authorization: Bearer <token>里。放在 URL 里调试时很直观,但如果 Token 出现在访问日志里,会泄露权限信息,所以正式生产环境建议用请求头。注意:Token 有时效性,短则几十分钟、长则数小时,具体看服务端配置。脚本里不要写死 Token,每次运行前重新获取,或者先判断expires时间再决定是否刷新。我见过不少线上故障都是因为凌晨定时任务跑的时候 Token 恰好过期,任务失败后又没有人看日志。3.2 Query 参数逐个拆解:where、outFields、returnGeometry查询操作是所有接口中使用频率最高的,但很多人喜欢直接复制一个“万能模板”,却不知道每个参数为什么这样写。我的习惯是逐个拆解:参数示例作用where1=1SQL 风格的过滤条件outFieldsNAME,CODE只返回需要的字段returnGeometrytrue是否返回几何信息outSR4326输出坐标系fjson返回格式举个最小示例:https://your-server/arcgis/rest/services/城市基础/MapServer/3/query ?where=1=1 &outFields=NAME,CODE &returnGeometry=true &outSR=4326 &f=jsonwhere=1=1是查询全部数据的经典写法。因为where必须是合法 SQL 表达式,1=1永远为真,所以等价于不设过滤。但它容易让前端把全量数据拉下来,正式环境一定要加上更严格的条件,比如按区域、状态字段过滤。outFields尽量只写需要的字段。有的图层有几十个字段,如果写*,返回的 JSON 会非常庞大。尤其在地图拖动、异步加载场景下,响应体积直接影响页面性能。returnGeometry=true表示返回每条要素的空间坐标。如果只是做属性校验或者统计数量,改成false,响应量会小很多。3.3 空间查询的参数组合:geometry、spatialRel、inSR纯属性查询比较简单,一旦涉及空间查询,参数就多了。ArcGIS REST API 里最常用的空间查询组合是geometry+geometryType+spatialRel+inSR。例如我在地图上画了一个矩形框,想查这个矩形框内所有设施点,请求是这样的:https://your-server/arcgis/rest/services/城市基础/MapServer/3/query ?where=1=1 &geometry=-122.2,37.4,-122.0,37.6 &geometryType=esriGeometryEnvelope &spatialRel=esriSpatialRelIntersects &inSR=4326 &outSR=4326 &outFields=NAME,CODE &f=json这里geometry的四组数字表示矩形框的xmin,ymin,xmax,ymax,geometryType=esriGeometryEnvelope告诉服务端这是一个外包矩形,inSR=4326声明这个矩形的坐标系,spatialRel=esriSpatialRelIntersects表示只要要素与矩形有交集就返回。空间关系还可以换成esriSpatialRelContains、esriSpatialRelWithin、esriSpatialRelTouches等。我的经验是:Intersects最常用,但要做严格的“完全包含”关系时,必须用Contains或Within,否则边界重叠的记录会被误判。注意:坐标顺序不能写反。写成ymin,xmin,ymax,xmax会返回空结果,因为服务端判断不了这个范围。排查空间查询无数据时,第一件事就是检查坐标顺序和inSR是否与前端坐标系一致。3.4 用 POST 做大量参数请求浏览器地址栏里能用 GET 调通的接口,在生产环境不一定适合。因为where复杂时中文、括号、单引号都要做 URL 编码,地址会变得很长,还容易被网关截断。我的建议是:凡是参数多的查询,统一用 POST 提交表单数据。Python 里用requests写起来很直观:import requests url = "https://your-server/arcgis/rest/services/城市基础/FeatureServer/0/query" params = { "where": "STATUS = '1' AND NAME LIKE '%测试%'", "outFields": "NAME,CODE,STATUS", "returnGeometry": "false", "outSR": "4326", "f": "json", "token": token, } resp = requests.post(url, data=params, timeout=30) data = resp.json() for feature in data.get("features", []): attrs = feature["attributes"] print(attrs["NAME"], attrs["CODE"])POST 方式对参数编码更友好,也方便统一管理超时、代理、重试逻辑。我在自动化脚本里基本都用 POST,只有临时调试才用 GET。4. 把接口记录变成能长期维护的文档前面讲的都是“怎么调通”,但在真实项目里,接口记录的价值在于“可维护”。服务更新、图层结构调整、坐标系变更,都会让之前的代码突然崩掉。所以我建议把 ArcGIS REST API 信息做成一张标准清单,而不是散落在聊天记录里。4.1 需要记录的元信息清单每接入一个服务,我至少记录以下内容:类别具体内容访问地址实际业务地址、原始服务地址服务类型MapServer / FeatureServer / ImageServer / GPServer图层编号同一个服务下多个图层各自的 ID图层名称图层显示名称,有些中文名称会重复几何类型点、线、面、多点原生坐标系服务数据源的 SR,比如3857、4326、4490关键字段业务主键、名称字段、状态字段权限状态是否需要 Token、编辑能力是否开启最大记录数maxRecordCount值更新时间服务最近一次验证的日期这些信息不需要手工复制,直接从服务目录 JSON 里读取就行。我经常做完一次服务盘点后,把结果存成 Markdown 表格,再附一份原始 JSON 作为附件,项目验收、交接新人时都很省事。4.2 用脚本自动盘点图层如果服务数量很少,手工记录没问题;但如果一个系统下挂了几十个服务、上百个图层,手工就不现实了。我会写一个递归脚本,从根目录开始遍历所有 services 和 folders,自动打印出每个服务的图层清单。import requests import json base_url = "https://your-server/arcgis/rest/services" token = "your_token" def list_layers(service_url): resp = requests.get(service_url, params={"f": "json", "token": token}) data = resp.json() for layer in data.get("layers", []): print(layer["id"], layer["name"], layer.get("geometryType")) for table in data.get("tables", []): print("table", table["id"], table["name"]) def walk_folders(folder_path="/"): url = base_url + folder_path resp = requests.get(url, params={"f": "json", "token": token}) data = resp.json() for service in data.get("services", []): print(service["name"], service["type"]) if service["type"] == "MapServer": list_layers(base_url + "/" + service["name"] + "/MapServer") elif service["type"] == "FeatureServer": list_layers(base_url + "/" + service["name"] + "/FeatureServer") for folder in data.get("folders", []): walk_folders(folder_path + "/" + folder)这个脚本可以做成定时巡检任务,每周跑一次,比较前后两次的图层列表。如果某个图层被删了、字段改了,脚本能最早发现,而不是等业务系统报错才知道。4.3 记录字段别名和编码域字段名通常是英文,别名才是业务人员认识的中文名。比如字段叫STATUS,别名是“状态”,值域里可能定义了0代表“停用”、1代表“在用”。写接口记录时,要把别名和值域也存下来。{ "name": "STATUS", "alias": "状态", "type": "esriFieldTypeString", "domain": { "type": "codedValue", "codedValues": [ { "name": "停用", "code": 0 }, { "name": "在用", "code": 1 } ] } }在前端展示时,我通常用别名做表头;写入数据时,再通过编码域把界面文字转成对应的数字或代码。这个转换逻辑如果不记录,后面接手的同事看到STATUS=0会完全不知道是什么意思。5. 常见问题与排查技巧实录ArcGIS REST API 的报错信息不像普通 Web 接口那么友好,很多提示是“Unable to complete operation”这种笼统描述。但大部分坑其实有固定套路,掌握之后能很快定位。5.1 error 400、498、499 代表什么HTTP 状态码是排查接口问题的第一现场。400 Bad Request:参数错误。通常是漏了必填参数、参数类型不对、where 语句语法有问题。做法是检查 URL 里的f=json、geometryType、outSR是否齐全。498 Invalid Token:Token 非法或已过期。重新生成 Token 再看。499 Token Required:请求需要 Token 但没传。确认是否在 URL 或请求头带了 Token,Token 是否被网关过滤。500 Internal Server Error:服务端处理失败,常见原因是数据源连接异常或查询条件太复杂。此时要看服务端日志,客户端往往拿不到详细堆栈。我见过一个很典型的 498 场景:脚本里用requests.get,默认把参数编码到 URL,但 Token 本身包含+/等特殊字符,在某些网关里会被解码改变。解决方法是把 Token 放到 Header 里,或者改用 POST 提交。5.2 中文查询结果为空中文条件过滤是高频问题。比如where=NAME LIKE '%北京%',如果直接在浏览器地址栏粘贴,中文空格会被浏览器部分转义,但送到服务端时编码不对,结果就是返回 0 条但也不报错。我的经验是,中文条件查询尽量用 POST,让 HTTP 库自动处理 UTF-8 编码。如果只能用 GET,那也要先对where做 URL 编码,再用requests或 Postman 调试。另外,where里的字符串值要用英文单引号包裹,例如:where=NAME LIKE '%北京%'这里的单引号不是中文引号,写错会导致 SQL 拼接错误。提示:如果返回结果为空,先在服务目录里手动执行一次f=json,确认数据源是否有数据。很多时候不是查询参数问题,而是这个图层本身就没有记录,或者图层被设置了权限过滤。5.3 返回要素太多,接口很慢ArcGIS REST API 的maxRecordCount限制了单次最多返回多少条,但即使只有 2000 条,如果每次带着完整几何和几十个字段返回,也会很慢。排查慢接口时,我会先做三件事:加returnCountOnly=true看看总数到底有多少,判断是不是全表查询。加空间过滤或where条件缩小范围。outFields只保留业务需要的字段,减少序列化体积。如果要分批拉取大量数据,分页参数是resultOffset和resultRecordCount。示例:https://your-server/arcgis/rest/services/城市基础/FeatureServer/0/query ?where=1=1 &resultOffset=0 &resultRecordCount=500 &outFields=OBJECTID,NAME &returnGeometry=false &f=json下一页就把resultOffset变成 500、1000。不过分页拉取时要注意数据是否在查询过程中被修改,否则会出现跳条或重复。对于只读分析场景,我更推荐用where=OBJECTID > 上次最大值的方式滚动读取,稳定性更好。5.4 坐标对不上,地图位置偏移地图位置偏移通常不是接口的问题,而是坐标系没对齐。服务源数据可能是3857,前端地图用4326,如果请求里没有outSR=4326,返回的就是以服务原始坐标系表达的点,前端叠加到另一套坐标系上自然错位。正确的做法是:查询接口统一加outSR,让它输出前端需要的坐标系空间查询输入时声明inSR,告诉服务端你传入的坐标是什么坐标系不要在浏览器前端再做一次神秘的“坐标偏移”魔法,容易越改越乱如果服务坐标是 CGCS2000 等国内常用坐标系,需要注意服务端版本是否支持对应的 WKID。有的老版本服务不识别某些新 WKID,会直接报错或返回空。这时要么换用同椭球下的近似坐标系,要么在服务端做一次数据源转换,两种情况都要在接口记录里写清楚“实测可用坐标系”。6. 一点个人经验和小工具记录做久了,多少会总结出一些顺手的小工具和习惯。放在最后,纯粹是因为这些东西不算必须的官方知识,但确实能提高效率。6.1 浏览器是最好用的接口调试器调试 ArcGIS REST API,我经常直接打开浏览器,在地址栏里访问服务地址,然后加?f=json。浏览器会把返回的 JSON 自动格式化,缩进、层级一目了然。Chrome 或 Edge 还支持直接在地址栏搜索 JSON 里的字段,比 Postman 还要快。需要把请求串起来组合查询时,我会先用浏览器的“开发者工具”里的网络面板,找到已经成功的查询请求,右键复制为fetch或cURL,再放进脚本里改参数。这样能保证请求头、编码方式和服务端实际接收到的请求完全一致。6.2 自动化巡检比被动维护省心我上面提到过定期跑目录遍历脚本,这里再补充一个实际用法:把脚本放到定时任务里,每天生成一份 JSON 快照。哪天有第三方系统反馈字段不对、图层丢失,我直接把昨天的快照和今天的快照做对比,几秒钟就能定位变更点。这个习惯成本很低,收益却很高。GIS 服务有时会被管理员在后台调整,例如给图层加一个字段、关掉一个能力,这些调整不会主动通知业务方。只有通过巡检才能提前发现问题。6.3 关于“记录”这件事,我的体会是它要一直更新我最早也以为 ArcGIS REST API 记一次就够了,后来发现不同服务器版本、不同数据源、不同服务配置,接口参数细节都会有一点差异。比如同样一个where条件,在某套环境里支持中文别名查询,在另一套环境里只支持字段英文名查询;同样一个空间查询,一个服务用Intersects能返回边界点,另一个服务可能需要Touches。所以我现在每接通一个服务,都会在接口记录末尾写上一行“实测日期、实测环境、可用参数组合”。这不是文档洁癖,而是知道这些信息在使用时才最可靠。希望这份 ArcGIS REST API 记录也能帮你少走几次弯路。