如何从MultiPolygon GeoJSON提取各城市独立的海岸/非交界边界线?
提取城市独立边界/海岸线的实现方案
完全可行,核心是识别多边形的非共享边——也就是仅属于单个城市的边界(对应你需求中的彩色线条)。以下是具体实现方法:
一、软件工具实现(QGIS)
无需编程,用开源GIS工具QGIS即可完成:
- 步骤1:加载你的GeoJSON文件到QGIS
- 步骤2:拆分MultiPolygon为单个Polygon:
打开Vector > Geometry Tools > Multipart to Singleparts,输入原图层,输出新的单多边形图层 - 步骤3:提取所有边界线:
打开Vector > Geometry Tools > Polygons to Lines,输入上一步的图层,输出所有边界线段图层 - 步骤4:识别共享边界:
- 打开
Vector > Geoprocessing Tools > Dissolve,勾选Dissolve all,合并所有线段 - 打开
Vector > Analysis Tools > Count Overlapping Features,输入合并后的图层,得到每条线段的重叠次数(即被多少城市共享)
- 打开
- 步骤5:筛选独立边界:
打开线段图层的属性表,筛选count = 1的记录,这些就是单个城市独有的边界 - 步骤6:按城市拆分保存:
打开Vector > Data Management Tools > Split vector layer,选择城市名称字段,设置输出文件夹,每个城市会生成独立的GeoJSON文件
二、编程实现
Python方案(Geopandas + Shapely)
适合批量处理或自动化场景,需要安装依赖:pip install geopandas shapely pandas
import geopandas as gpd from shapely.geometry import LineString from shapely.ops import unary_union, linemerge # 加载GeoJSON数据 gdf = gpd.read_file("你的城市数据.geojson") # 1. 提取所有城市的边界线段(拆分为单条线段) all_lines = [] for idx, row in gdf.iterrows(): city_name = row["city"] # 替换为你数据中的城市名字段 # 遍历MultiPolygon中的每个Polygon for polygon in row.geometry.geoms: # 处理外环(海岸线/主边界) exterior_coords = list(polygon.exterior.coords) for i in range(len(exterior_coords)-1): line = LineString([exterior_coords[i], exterior_coords[i+1]]) all_lines.append({"city": city_name, "geometry": line}) # 可选:处理内环(如城市内的湖泊边界) # for interior in polygon.interiors: # interior_coords = list(interior.coords) # for i in range(len(interior_coords)-1): # line = LineString([interior_coords[i], interior_coords[i+1]]) # all_lines.append({"city": city_name, "geometry": line}) # 转为GeoDataFrame lines_gdf = gpd.GeoDataFrame(all_lines, crs=gdf.crs) # 2. 统计每条线段的共享城市数量 merged_lines = unary_union(lines_gdf.geometry) unique_lines = list(merged_lines.geoms) line_counts = [] for line in unique_lines: # 匹配重合线段(设置精度容差避免浮点误差) matches = lines_gdf[lines_gdf.geometry.equals_exact(line, tolerance=0.0001)] unique_cities = matches["city"].unique() line_counts.append({ "geometry": line, "count": len(unique_cities), "cities": unique_cities.tolist() }) counts_gdf = gpd.GeoDataFrame(line_counts, crs=gdf.crs) # 3. 筛选非共享线段并按城市保存 non_shared_lines = counts_gdf[counts_gdf["count"] == 1] for city in non_shared_lines["cities"].apply(lambda x: x[0]).unique(): city_segments = non_shared_lines[non_shared_lines["cities"].apply(lambda x: x[0] == city)] # 合并同城市的连续线段(可选) merged_city_line = linemerge(unary_union(city_segments.geometry)) # 保存为GeoJSON city_gdf = gpd.GeoDataFrame({"city": [city], "geometry": [merged_city_line]}, crs=gdf.crs) city_gdf.to_file(f"{city}_独立边界.geojson", driver="GeoJSON")
JS方案(Turf.js)
适合前端或Node.js环境,依赖安装:npm install @turf/turf(Node.js),前端可直接引入CDN
const turf = require('@turf/turf'); const fs = require('fs'); // 加载GeoJSON数据 const cityData = JSON.parse(fs.readFileSync('你的城市数据.geojson', 'utf8')); // 1. 提取所有城市的边界线段 const allLines = []; cityData.features.forEach(feature => { const cityName = feature.properties.city; // 替换为你数据中的城市名字段 // 处理MultiPolygon的每个Polygon feature.geometry.coordinates.forEach(polygonCoords => { // 处理外环 const exterior = polygonCoords[0]; for (let i = 0; i < exterior.length - 1; i++) { const line = turf.lineString([exterior[i], exterior[i+1]], {city: cityName}); allLines.push(line); } // 可选:处理内环 // for (let j = 1; j < polygonCoords.length; j++) { // const interior = polygonCoords[j]; // for (let i = 0; i < interior.length - 1; i++) { // const line = turf.lineString([interior[i], interior[i+1]], {city: cityName}); // allLines.push(line); // } // } }); }); // 2. 统计每条线段的共享城市数 const mergedLines = turf.lineMerge(turf.featureCollection(allLines)); const uniqueLines = turf.lineSplit(mergedLines, mergedLines).features; const lineStats = uniqueLines.map(line => { // 匹配重合线段(截断精度避免浮点误差) const matches = allLines.filter(l => turf.booleanEqual(turf.truncate(l, {precision: 6}), turf.truncate(line, {precision: 6}))); const uniqueCities = [...new Set(matches.map(l => l.properties.city))]; return { ...line, properties: { count: uniqueCities.length, cities: uniqueCities } }; }); // 3. 筛选非共享线段并按城市分组保存 const nonSharedLines = lineStats.filter(stat => stat.properties.count === 1); const cityGroups = {}; nonSharedLines.forEach(line => { const city = line.properties.cities[0]; cityGroups[city] = cityGroups[city] || []; cityGroups[city].push(line); }); // 保存每个城市的边界文件 Object.keys(cityGroups).forEach(city => { const featureCollection = turf.featureCollection(cityGroups[city]); fs.writeFileSync(`${city}_独立边界.geojson`, JSON.stringify(featureCollection, null, 2)); });
内容的提问来源于stack exchange,提问作者good112233
相关产品推荐
相关产品推荐

