第17章 - 实战案例分析
第17章:实战案例分析
17.1 概述
本章通过实际项目案例,展示如何使用 GeoTools 解决实际地理空间问题。每个案例都提供完整的代码实现和详细说明。
17.2 案例一:在线地图服务系统
17.2.1 项目需求
构建一个基于 Web 的地图服务系统:
- 支持多种数据源(Shapefile、PostGIS、GeoJSON)
- 实现 WMS 和 WFS 服务接口
- 动态样式渲染
- 空间查询功能
17.2.2 核心实现
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.FeatureLayer;
import org.geotools.map.MapContent;
import org.geotools.renderer.GTRenderer;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.Style;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
public class WebMapService {
/**
* 生成 WMS GetMap 响应
*/
public BufferedImage generateMap(
String dataSource,
ReferencedEnvelope bbox,
int width,
int height,
Style style) throws Exception {
// 连接数据源
Map<String, Object> params = new HashMap<>();
params.put("url", new File(dataSource).toURI().toURL());
DataStore dataStore = DataStoreFinder.getDataStore(params);
SimpleFeatureSource featureSource =
dataStore.getFeatureSource(dataStore.getTypeNames()[0]);
// 创建地图内容
MapContent mapContent = new MapContent();
mapContent.setTitle("Web Map");
// 添加图层
FeatureLayer layer = new FeatureLayer(featureSource, style);
mapContent.addLayer(layer);
// 创建渲染器
GTRenderer renderer = new StreamingRenderer();
renderer.setMapContent(mapContent);
// 创建图像
BufferedImage image = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
// 设置渲染参数
Rectangle imageBounds = new Rectangle(0, 0, width, height);
// 渲染地图
renderer.paint(graphics, imageBounds, bbox);
// 清理
graphics.dispose();
mapContent.dispose();
dataStore.dispose();
return image;
}
/**
* WMS GetCapabilities 响应
*/
public String getCapabilities(String serviceUrl) {
StringBuilder xml = new StringBuilder();
xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
xml.append("<WMS_Capabilities version=\"1.3.0\">\n");
xml.append(" <Service>\n");
xml.append(" <Name>WMS</Name>\n");
xml.append(" <Title>GeoTools Web Map Service</Title>\n");
xml.append(" <OnlineResource xlink:href=\"").append(serviceUrl).append("\"/>\n");
xml.append(" </Service>\n");
xml.append(" <Capability>\n");
xml.append(" <Request>\n");
xml.append(" <GetMap>\n");
xml.append(" <Format>image/png</Format>\n");
xml.append(" <Format>image/jpeg</Format>\n");
xml.append(" </GetMap>\n");
xml.append(" </Request>\n");
xml.append(" </Capability>\n");
xml.append("</WMS_Capabilities>");
return xml.toString();
}
}
17.2.3 Spring Boot 集成
import org.springframework.web.bind.annotation.*;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@RestController
@RequestMapping("/wms")
public class WMSController {
private final WebMapService mapService = new WebMapService();
@GetMapping(produces = MediaType.IMAGE_PNG_VALUE)
public ResponseEntity<byte[]> getMap(
@RequestParam String layers,
@RequestParam String bbox,
@RequestParam int width,
@RequestParam int height,
@RequestParam(defaultValue = "EPSG:4326") String srs) {
try {
// 解析边界框
String[] coords = bbox.split(",");
ReferencedEnvelope envelope = new ReferencedEnvelope(
Double.parseDouble(coords[0]),
Double.parseDouble(coords[2]),
Double.parseDouble(coords[1]),
Double.parseDouble(coords[3]),
CRS.decode(srs)
);
// 生成地图
BufferedImage image = mapService.generateMap(
layers, envelope, width, height, createDefaultStyle()
);
// 转换为字节数组
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "png", baos);
return ResponseEntity.ok()
.contentType(MediaType.IMAGE_PNG)
.body(baos.toByteArray());
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@GetMapping(params = "request=GetCapabilities")
public ResponseEntity<String> getCapabilities() {
String capabilities = mapService.getCapabilities(
"http://localhost:8080/wms"
);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_XML)
.body(capabilities);
}
}
17.3 案例二:空间数据分析平台
17.3.1 缓冲区分析工具
import org.geotools.data.*;
import org.geotools.data.simple.*;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.geometry.jts.JTS;
import org.locationtech.jts.geom.*;
public class SpatialAnalysisTool {
/**
* 缓冲区分析
*/
public void bufferAnalysis(
String inputPath,
String outputPath,
double distance) throws Exception {
// 读取输入数据
FileDataStore inputStore = FileDataStoreFinder.getDataStore(
new File(inputPath)
);
SimpleFeatureSource inputSource = inputStore.getFeatureSource();
SimpleFeatureCollection inputFeatures = inputSource.getFeatures();
// 创建输出要素类型
SimpleFeatureType outputType = DataUtilities.createType(
"Buffer",
"the_geom:Polygon:srid=4326,id:Integer,distance:Double"
);
// 创建输出数据存储
FileDataStoreFactorySpi factory = new ShapefileDataStoreFactory();
Map<String, Serializable> params = new HashMap<>();
params.put("url", new File(outputPath).toURI().toURL());
params.put("create spatial index", Boolean.TRUE);
ShapefileDataStore outputStore =
(ShapefileDataStore) factory.createNewDataStore(params);
outputStore.createSchema(outputType);
// 执行缓冲区分析
SimpleFeatureBuilder featureBuilder =
new SimpleFeatureBuilder(outputType);
Transaction transaction = new DefaultTransaction("buffer");
SimpleFeatureStore featureStore =
(SimpleFeatureStore) outputStore.getFeatureSource();
featureStore.setTransaction(transaction);
try {
SimpleFeatureIterator iterator = inputFeatures.features();
int id = 0;
while (iterator.hasNext()) {
SimpleFeature feature = iterator.next();
Geometry geometry = (Geometry) feature.getDefaultGeometry();
// 执行缓冲区操作
Geometry buffer = geometry.buffer(distance);
// 创建输出要素
featureBuilder.add(buffer);
featureBuilder.add(id++);
featureBuilder.add(distance);
SimpleFeature bufferFeature = featureBuilder.buildFeature(null);
featureStore.addFeatures(
DataUtilities.collection(bufferFeature)
);
}
transaction.commit();
System.out.println("缓冲区分析完成: " + id + " 个要素");
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
transaction.close();
inputStore.dispose();
outputStore.dispose();
}
}
/**
* 空间叠加分析
*/
public void overlayAnalysis(
String layer1Path,
String layer2Path,
String outputPath,
String operation) throws Exception {
// 读取两个图层
SimpleFeatureCollection features1 = readFeatures(layer1Path);
SimpleFeatureCollection features2 = readFeatures(layer2Path);
// 创建输出
List<SimpleFeature> resultFeatures = new ArrayList<>();
SimpleFeatureIterator iterator1 = features1.features();
while (iterator1.hasNext()) {
SimpleFeature f1 = iterator1.next();
Geometry g1 = (Geometry) f1.getDefaultGeometry();
SimpleFeatureIterator iterator2 = features2.features();
while (iterator2.hasNext()) {
SimpleFeature f2 = iterator2.next();
Geometry g2 = (Geometry) f2.getDefaultGeometry();
Geometry result = null;
switch (operation.toLowerCase()) {
case "intersection":
result = g1.intersection(g2);
break;
case "union":
result = g1.union(g2);
break;
case "difference":
result = g1.difference(g2);
break;
}
if (result != null && !result.isEmpty()) {
// 创建结果要素
SimpleFeatureBuilder builder =
new SimpleFeatureBuilder(features1.getSchema());
builder.add(result);
resultFeatures.add(builder.buildFeature(null));
}
}
iterator2.close();
}
iterator1.close();
// 保存结果
saveFeatures(resultFeatures, outputPath);
System.out.println("叠加分析完成: " + resultFeatures.size() + " 个要素");
}
private SimpleFeatureCollection readFeatures(String path) throws Exception {
FileDataStore store = FileDataStoreFinder.getDataStore(new File(path));
SimpleFeatureSource source = store.getFeatureSource();
return source.getFeatures();
}
private void saveFeatures(List<SimpleFeature> features, String path)
throws Exception {
// 实现保存逻辑
}
}
17.4 案例三:GPS 轨迹分析系统
17.4.1 轨迹处理工具
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.*;
import org.opengis.referencing.operation.MathTransform;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
public class GPSTrackAnalyzer {
static class TrackPoint {
double longitude;
double latitude;
LocalDateTime timestamp;
double speed; // m/s
public TrackPoint(double lon, double lat, LocalDateTime time) {
this.longitude = lon;
this.latitude = lat;
this.timestamp = time;
}
}
static class TrackStatistics {
double totalDistance; // 总距离 (米)
double averageSpeed; // 平均速度 (km/h)
double maxSpeed; // 最大速度 (km/h)
long duration; // 持续时间 (秒)
double minElevation;
double maxElevation;
}
/**
* 计算轨迹统计信息
*/
public TrackStatistics analyzeTrack(List<TrackPoint> points)
throws Exception {
TrackStatistics stats = new TrackStatistics();
if (points.size() < 2) {
return stats;
}
// 创建坐标转换(WGS84 到 UTM)
GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
double totalDistance = 0;
double maxSpeed = 0;
for (int i = 1; i < points.size(); i++) {
TrackPoint p1 = points.get(i - 1);
TrackPoint p2 = points.get(i);
// 计算两点间距离
Coordinate c1 = new Coordinate(p1.longitude, p1.latitude);
Coordinate c2 = new Coordinate(p2.longitude, p2.latitude);
Point point1 = geometryFactory.createPoint(c1);
Point point2 = geometryFactory.createPoint(c2);
double distance = JTS.orthodromicDistance(c1, c2,
CRS.decode("EPSG:4326"));
totalDistance += distance;
// 计算速度
long timeDiff = java.time.Duration.between(
p1.timestamp, p2.timestamp
).getSeconds();
if (timeDiff > 0) {
double speed = (distance / timeDiff) * 3.6; // 转换为 km/h
p2.speed = speed;
maxSpeed = Math.max(maxSpeed, speed);
}
}
// 计算统计信息
stats.totalDistance = totalDistance;
stats.maxSpeed = maxSpeed;
long duration = java.time.Duration.between(
points.get(0).timestamp,
points.get(points.size() - 1).timestamp
).getSeconds();
stats.duration = duration;
stats.averageSpeed = duration > 0 ?
(totalDistance / duration) * 3.6 : 0;
return stats;
}
/**
* 轨迹简化(Douglas-Peucker 算法)
*/
public List<TrackPoint> simplifyTrack(
List<TrackPoint> points,
double tolerance) {
if (points.size() < 3) {
return new ArrayList<>(points);
}
GeometryFactory factory = JTSFactoryFinder.getGeometryFactory();
// 转换为 LineString
Coordinate[] coords = points.stream()
.map(p -> new Coordinate(p.longitude, p.latitude))
.toArray(Coordinate[]::new);
LineString lineString = factory.createLineString(coords);
// 简化
Geometry simplified =
org.locationtech.jts.simplify.DouglasPeuckerSimplifier
.simplify(lineString, tolerance);
// 转换回 TrackPoint
List<TrackPoint> result = new ArrayList<>();
for (Coordinate coord : simplified.getCoordinates()) {
// 查找原始点
for (TrackPoint point : points) {
if (Math.abs(point.longitude - coord.x) < 0.000001 &&
Math.abs(point.latitude - coord.y) < 0.000001) {
result.add(point);
break;
}
}
}
return result;
}
/**
* 识别停留点
*/
public List<Point> findStayPoints(
List<TrackPoint> points,
double distanceThreshold, // 米
long timeThreshold) { // 秒
List<Point> stayPoints = new ArrayList<>();
GeometryFactory factory = JTSFactoryFinder.getGeometryFactory();
int i = 0;
while (i < points.size()) {
int j = i + 1;
while (j < points.size()) {
TrackPoint p1 = points.get(i);
TrackPoint p2 = points.get(j);
Coordinate c1 = new Coordinate(p1.longitude, p1.latitude);
Coordinate c2 = new Coordinate(p2.longitude, p2.latitude);
double distance = JTS.orthodromicDistance(c1, c2,
CRS.decode("EPSG:4326"));
long timeDiff = java.time.Duration.between(
p1.timestamp, p2.timestamp
).getSeconds();
if (distance < distanceThreshold &&
timeDiff >= timeThreshold) {
// 找到停留点,计算中心点
double centerLon = (p1.longitude + p2.longitude) / 2;
double centerLat = (p1.latitude + p2.latitude) / 2;
stayPoints.add(factory.createPoint(
new Coordinate(centerLon, centerLat)
));
i = j;
break;
}
if (distance >= distanceThreshold) {
break;
}
j++;
}
i++;
}
return stayPoints;
}
}
17.5 案例四:地理编码服务
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.filter.text.cql2.CQL;
import org.opengis.filter.Filter;
public class GeocodingService {
static class GeocodingResult {
String address;
double longitude;
double latitude;
double confidence; // 0-1
}
/**
* 地址正向编码
*/
public List<GeocodingResult> geocode(String address) throws Exception {
List<GeocodingResult> results = new ArrayList<>();
// 连接地址数据库
SimpleFeatureSource addressSource = getAddressDataSource();
// 构建查询过滤器
String filterString = String.format(
"address_text LIKE '%%%s%%'", address
);
Filter filter = CQL.toFilter(filterString);
SimpleFeatureCollection features = addressSource.getFeatures(filter);
SimpleFeatureIterator iterator = features.features();
while (iterator.hasNext()) {
SimpleFeature feature = iterator.next();
Point point = (Point) feature.getDefaultGeometry();
GeocodingResult result = new GeocodingResult();
result.address = (String) feature.getAttribute("full_address");
result.longitude = point.getX();
result.latitude = point.getY();
result.confidence = calculateConfidence(address, result.address);
results.add(result);
}
iterator.close();
// 按置信度排序
results.sort((a, b) ->
Double.compare(b.confidence, a.confidence)
);
return results;
}
/**
* 逆向地理编码
*/
public String reverseGeocode(double longitude, double latitude,
double radius) throws Exception {
GeometryFactory factory = JTSFactoryFinder.getGeometryFactory();
Point point = factory.createPoint(new Coordinate(longitude, latitude));
// 创建缓冲区
Geometry buffer = point.buffer(radius / 111000.0); // 度
// 查询最近的地址
SimpleFeatureSource addressSource = getAddressDataSource();
Filter spatialFilter = FF.intersects(
FF.property("the_geom"),
FF.literal(buffer)
);
SimpleFeatureCollection features = addressSource.getFeatures(spatialFilter);
// 找到最近的地址
SimpleFeatureIterator iterator = features.features();
String closestAddress = null;
double minDistance = Double.MAX_VALUE;
while (iterator.hasNext()) {
SimpleFeature feature = iterator.next();
Point addressPoint = (Point) feature.getDefaultGeometry();
double distance = point.distance(addressPoint);
if (distance < minDistance) {
minDistance = distance;
closestAddress = (String) feature.getAttribute("full_address");
}
}
iterator.close();
return closestAddress;
}
private double calculateConfidence(String query, String result) {
// 简单的相似度计算
int matches = 0;
String[] queryWords = query.toLowerCase().split("\\s+");
String resultLower = result.toLowerCase();
for (String word : queryWords) {
if (resultLower.contains(word)) {
matches++;
}
}
return (double) matches / queryWords.length;
}
private SimpleFeatureSource getAddressDataSource() throws Exception {
// 实现数据源连接
return null;
}
}
17.6 本章小结
本章通过四个实战案例展示了 GeoTools 的实际应用:
- 在线地图服务:实现标准的 WMS/WFS 服务
- 空间数据分析:缓冲区分析和空间叠加
- GPS 轨迹分析:轨迹统计、简化和停留点识别
- 地理编码服务:正向和逆向地理编码
每个案例都提供了完整、可运行的代码,可直接应用到实际项目中。
相关资源:

浙公网安备 33010602011771号