第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 的实际应用:

  1. 在线地图服务:实现标准的 WMS/WFS 服务
  2. 空间数据分析:缓冲区分析和空间叠加
  3. GPS 轨迹分析:轨迹统计、简化和停留点识别
  4. 地理编码服务:正向和逆向地理编码

每个案例都提供了完整、可运行的代码,可直接应用到实际项目中。


相关资源

posted @ 2025-12-29 11:40  我才是银古  阅读(5)  评论(0)    收藏  举报