简介
概述:GeoTools 是一个开源 (LGPL) Java 代码库,它为操作地理空间数据提供符合标准的方法,例如实现地理信息系统。GeoTools 库数据结构基于开放地理空间联盟 (OGC) 规范。
官网地址:
https://www.geotools.org/
常用maven库地址:
https://repo.osgeo.org/repository/release/
https://maven.geo-solutions.it/
数据支持
栅格数据
arcgrid
geotiff
grassraster
image ( JPEG TIFF GIF PNG)
imageio-ext-gdal
imagemosaic
imagepyramid
JP2K
matlab
矢量数据格式
wkt
csv
wkb
geojson
shapefile
数据库支持
db2
hana
h2
mysql
oracle
postgis
sqlserver
elastic search
Geotools使用
矢量数据
- 读取shp格式
public static void readShpFile(String shpPath) {File shpFile = new File(shpPath);try {ShapefileDataStore shapefileDataStore = new ShapefileDataStore(shpFile.toURI().toURL());// 设置编码,防止属性的中文字符出现乱码shapefileDataStore.setCharset(Charset.forName("UTF-8"));// 这个typeNamae不传递,默认是文件名称FeatureSource featuresource = shapefileDataStore.getFeatureSource(shapefileDataStore.getTypeNames()[0]);// 读取bboxReferencedEnvelope bbox =featuresource.getBounds();// 读取投影CoordinateReferenceSystem crs = featuresource.getSchema().getCoordinateReferenceSystem();// 特征总数int count = featuresource.getCount(Query.ALL);// 获取当前数据的geometry类型(点、线、面)GeometryType geometryType = featuresource.getSchema().getGeometryDescriptor().getType();// 读取要素SimpleFeatureCollection simpleFeatureCollection = (SimpleFeatureCollection) featuresource.getFeatures();// 获取当前矢量数据有哪些属性字段值List<AttributeDescriptor> attributes = simpleFeatureCollection.getSchema().getAttributeDescriptors();// 拿到迭代器SimpleFeatureIterator simpleFeatureIterator = simpleFeatureCollection.features();// 遍历每一个要素while(simpleFeatureIterator.hasNext()) {SimpleFeature simpleFeature = simpleFeatureIterator.next();// java8新特性流apiattributes.stream().forEach((a) -> {// 依次读取这个shape中每一个属性值,当然这个属性值,可以处理其它业务System.out.println(a.getLocalName() + ":" + simpleFeature.getAttribute(a.getLocalName()));});}} catch (IOException e) {e.printStackTrace();}System.out.println("读取完成!");}
- 读取wkb格式
ex : 0000010300000001000000A4000000B98D06F0163C5E408D28ED0DBE684440B98D06F0163C5E408D28ED0DBE684440B98D06F0163C5E408D28ED0DBE684440B98D06F0163C5E408D28ED0DBE684440075F984C153C5E4029C
WKBReader wkbReader = new WKBReader();
Geometry geometry = wkbReader.read(WKBReader.hexToBytes(tokens[i]));
- 读取wkt
ex :
MultiPolygon ( ((10 10, 10 20, 20 20, 20 15, 10 10)), ((60 60, 70 70, 80 60, 60 60 )) )
WKTReader wktReader = new WKTReader();
Geometry geometry = wktReader.read(wktStr);
- 读取geojson
ex :
{“type”:“Feature”,“crs”:{“type”:“name”,“properties”:{“name”:“EPSG:2380”}},“geometry”:{“type”:“MultiPolygon”,“coordinates”:[[[[646398.9535,3267941.9664],[649558.7196,3267895.3528],[649674.763,3265683.4124],[646387.8773,3265827.4858],[646398.9535,3267941.9664]]]]},“properties”:{“Id”:0}}
public static void readGeoJson(String jsonPath) throws IOException {File file = new File(jsonPath);FileInputStream fileInputStream = new FileInputStream(file);// 这里可能存在问题,如果是超大文件怎么办,一次性读入会不会报内存// 解决方案是不是对大文件进行拆分GeoJSONReader geoJSONReader = new GeoJSONReader(fileInputStream);SimpleFeatureCollection featureCollection = geoJSONReader.getFeatures();SimpleFeatureIterator iterator = featureCollection.features();List<AttributeDescriptor> attributes = featureCollection.getSchema().getAttributeDescriptors();while (iterator.hasNext()) {SimpleFeature simpleFeature = iterator.next();System.out.println();attributes.stream().forEach((a) -> {// 依次读取这个shape中每一个属性值,当然这个属性值,可以处理其它业务System.out.println(a.getLocalName() + ":" + simpleFeature.getAttribute(a.getLocalName()));});}fileInputStream.close();System.out.println("读取JSON完毕!");}
- 遍历要素及属性
SimpleFeatureCollection simpleFeatureCollection = (SimpleFeatureCollection) featuresource.getFeatures();
// 获取当前矢量数据有哪些属性字段值
List<AttributeDescriptor> attributes = simpleFeatureCollection.getSchema().getAttributeDescriptors();
// 拿到迭代器
SimpleFeatureIterator simpleFeatureIterator = simpleFeatureCollection.features();
// 遍历每一个要素
while(simpleFeatureIterator.hasNext()) {SimpleFeature simpleFeature = simpleFeatureIterator.next();// java8新特性流apiattributes.stream().forEach((a) -> {// 依次读取这个shape中每一个属性值,当然这个属性值,可以处理其它业务System.out.println(a.getLocalName() + ":" + simpleFeature.getAttribute(a.getLocalName()));});
}
- 新增要素(创建图层)
1.构造TYPE(定义数据的坐标参考、属性字段)
public static SimpleFeatureType createType(Class<?> c, String layerName) {SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();builder.setCRS(DefaultGeographicCRS.WGS84);builder.add("FID",String.class);// 需要注意的是几何字段的属性名称是固定的builder.add("the_geom", c);// 设置了图层的名字builder.setName(layerName);SimpleFeatureType simpleFeatureType = builder.buildFeatureType();return simpleFeatureType;}
Class的取值可以是:几何(Point.class,Polygon.class,MultiPolygon.class),属性(String.class,Integer.class, Double.class)
2.根据TYPE构建单个要素
SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(simpleFeatureType);
WKTReader wktReader = new WKTReader();
Geometry geometry = wktReader.read(wktStr);
// 这里的添加顺序和上面TYPE的时候保持一致
featureBuilder.add("1");
featureBuilder.add(geometry);
SimpleFeature feature = featureBuilder.buildFeature(null);
3.创建FeatureCollection
List<SimpleFeature> features = new ArrayList<>();// 只需要将上面步骤的单个要素放入循环中创建更多的要素features.add(feature);SimpleFeatureCollection collection = new ListFeatureCollection(TYPE, features);
4.导出为shp
// 生成shpfile
File shpFile = new File(shpPath);
ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
// 创造shpstore需要的参数
Map<String, Serializable> params = new HashMap<>();
params.put("url", shpFile.toURI().toURL());
params.put("create spatial index", Boolean.TRUE);
ShapefileDataStore newDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
newDataStore.createSchema(simpleFeatureType);
Transaction transaction = new DefaultTransaction("create");
String typeName = newDataStore.getTypeNames()[0];
SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);
SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
SimpleFeatureCollection collection = new ListFeatureCollection(simpleFeatureType, features);
featureStore.setTransaction(transaction);
featureStore.addFeatures(collection);
featureStore.setTransaction(transaction);
transaction.commit();
transaction.close();
拓展:
我们看到创建ShapefileDataStore 的时候传递了一个params ,那么这些参数可以传递那些值呢?可以看看源码, 可以看到里面内置了很多参数可用,我们上面使用的就是其中的两个参数。
public static final Param URLP = new Param("url", URL.class, "url to a .shp file", true, (Object)null, new KVP(new Object[]{"ext", "shp"}));
public static final Param NAMESPACEP = new Param("namespace", URI.class, "uri to a the namespace", false, (Object)null, new KVP(new Object[]{"level", "advanced"}));
public static final Param MEMORY_MAPPED = new Param("memory mapped buffer", Boolean.class, "enable/disable the use of memory-mapped io", false, false, new KVP(new Object[]{"level", "advanced"}));
public static final Param CACHE_MEMORY_MAPS = new Param("cache and reuse memory maps", Boolean.class, "only memory map a file one, then cache and reuse the map", false, true, new KVP(new Object[]{"level", "advanced"}));
public static final Param FILE_TYPE = new Param("filetype", String.class, "Discriminator for directory stores", false, "shapefile", new KVP(new Object[]{"level", "program"}));
public static final Param CREATE_SPATIAL_INDEX = new Param("create spatial index", Boolean.class, "enable/disable the automatic creation of spatial index", false, true, new KVP(new Object[]{"level", "advanced"}));
public static final Param DBFCHARSET = new Param("charset", Charset.class, "character used to decode strings from the DBF file", false, Charset.forName("ISO-8859-1"), new KVP(new Object[]{"level", "advanced"})) {
public static final Param FSTYPE = new Param("fstype", String.class, "Enable using a setting of 'shape'.", false, "shape", new KVP(new Object[]{"level", "advanced", "options", Arrays.asList("shape-ng", "shape", "index")}));
public static final Param DBFTIMEZONE = new Param("timezone", TimeZone.class, "time zone used to read dates from the DBF file", false, TimeZone.getDefault(), new KVP(new Object[]{"level", "advanced"})) {
public static final Param ENABLE_SPATIAL_INDEX = new Param("enable spatial index", Boolean.class, "enable/disable the use of spatial index for local shapefiles", false, true, new KVP(new Object[]{"level", "advanced"}));
栅格数据
- 读取TIFF
public static Coverage readTiff(String tiffPath) throws IOException {File f = new File(tiffPath);ParameterValue<OverviewPolicy> policy = AbstractGridFormat.OVERVIEW_POLICY.createValue();policy.setValue(OverviewPolicy.IGNORE);ParameterValue<String> gridsize = AbstractGridFormat.SUGGESTED_TILE_SIZE.createValue();ParameterValue<Boolean> useJaiRead = AbstractGridFormat.USE_JAI_IMAGEREAD.createValue();useJaiRead.setValue(true);GridCoverage2D image = new GeoTiffReader(f).read(new GeneralParameterValue[]{policy, gridsize, useJaiRead});return image;}
- 生成TIFF
File file = new File(outTiffPath);
GeoTiffWriter geoTiffWriter = new GeoTiffWriter(file);
final GeoTiffFormat format = new GeoTiffFormat();
final GeoTiffWriteParams wp = new GeoTiffWriteParams();
// 设置写出参数
wp.setCompressionMode(GeoTiffWriteParams.MODE_DEFAULT);
wp.setTilingMode(GeoToolsWriteParams.MODE_DEFAULT);
ParameterValueGroup paramWrite = format.getWriteParameters();
paramWrite.parameter(AbstractGridFormat.GEOTOOLS_WRITE_PARAMS.getName().toString()).setValue(wp);
geoTiffWriter.write((GridCoverage) coverage, paramWrite.values().toArray(new GeneralParameterValue[4]) );
geoTiffWriter.dispose();
- 通过掩膜提取
private static Coverage clipImageToFeatureSource() throws IOException, FactoryException, MismatchedDimensionException, TransformException {SimpleFeatureCollection collection = CommonMethod.readFeatureCollection("E:\\data\\shp\\mask.shp");FeatureIterator<SimpleFeature> iterator = collection.features();List<Geometry> all = new ArrayList<Geometry>();try {while (iterator.hasNext()) {SimpleFeature feature = iterator.next();Geometry geometry = (Geometry) feature.getDefaultGeometry();all.add(geometry);}} finally {if (iterator != null) {iterator.close();}}Coverage coverage = readTiff();Coverage clippedCoverage = null;if (all.size() > 0) {CoverageProcessor processor = new CoverageProcessor();ParameterValueGroup params = processor.getOperation("CoverageCrop").getParameters();params.parameter("Source").setValue(coverage);GeometryFactory factory = JTSFactoryFinder.getGeometryFactory(null);Geometry[] a = all.toArray(new Geometry[0]);GeometryCollection c = new GeometryCollection(a, factory);Envelope envelope = all.get(0).getEnvelopeInternal();double x1 = envelope.getMinX();double y1 = envelope.getMinY();double x2 = envelope.getMaxX();double y2 = envelope.getMaxY();ReferencedEnvelope referencedEnvelope = new ReferencedEnvelope(x1,x2, y1, y2, coverage.getCoordinateReferenceSystem());params.parameter("ENVELOPE").setValue(referencedEnvelope);params.parameter("ROI").setValue(c);params.parameter("ForceMosaic").setValue(true);clippedCoverage = processor.doOperation(params);}if (all.size() == 0){System.out.println("Crop by shapefile requested but no simple features matched extent!");}return clippedCoverage;
}
提取文件拓扑关系:
运行结果:
过滤器(属性查询,空间查询)
- 属性查询
// 得到图层所有要素
SimpleFeatureCollection collection = readFeatureCollection(shpPath);
// 通过过滤器进行筛选
SimpleFeatureCollection newCollection = collection.subCollection(filter);
得到过滤器的两种方式:
方式一:
// 直接书写筛选条件
Filter filter = CQL.toFilter("POP_RANK >= 5");
方式二:
// 使用工厂构造过滤器
FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
// greater表示大于
Filter filter = ff.greater(ff.property("POP_RANK"), ff.literal(5));
其它比较关系(非常多,可以进入类里面去查看支持那些关系比较):
- 空间查询
它的查询原理与属性查询相同,知识表达式需要写成空间表达式。
// 相交
Filter filter = CQL.toFilter( "INTERSECTS(the_geom," + wkt + ")");
// 在10公里以内
Filter filter = CQL.toFilter( "DWITHIN(the_geom," + wkt + ", 10, kilometers)");
完整代码:
SimpleFeatureCollection china = readFeatureCollection("E:\\data\\shp\\china.shp");
SimpleFeatureCollection cities = readFeatureCollection("E:\\data\\shp\\cities.shp");
SimpleFeatureIterator chinaIterator = china.features();
Geometry geometry = null;
while (chinaIterator.hasNext()) {SimpleFeature next = chinaIterator.next();geometry = (Geometry) next.getDefaultGeometry();
}
Filter filter = getSpatialFilter(geometry);
SimpleFeatureCollection subCollection = cities.subCollection(filter);
更多用法参考:
https://docs.geotools.org/stable/userguide/library/cql/cql.html
空间分析
- 裁剪
public static void clip() {SimpleFeatureCollection china = readFeatureCollection("E:\\data\\shp\\china.shp");SimpleFeatureCollection countries = readFeatureCollection("E:\\data\\shp\\countries.shp");SimpleFeature next = china.features().next();Geometry geometry = (Geometry) next.getDefaultGeometry();ClippedFeatureCollection clippedFeatureCollection = new ClippedFeatureCollection(countries, geometry, true);SimpleFeatureIterator clipedFeatures = clippedFeatureCollection.features();int gcount = 0;while (clipedFeatures.hasNext()) {SimpleFeature feature = clipedFeatures.next();Collection<Property> properties = feature.getProperties();Iterator<Property> iterator = properties.iterator();while (iterator.hasNext()) {Property property = iterator.next();System.out.println(property.getName() + " " + property.getValue());}gcount ++;}System.out.println("裁剪后还剩下的元素!" + gcount);}
- 缓冲区
public static void buffer() throws IOException, FactoryException {SimpleFeatureCollection cities = readFeatureCollection("E:\\data\\shp\\cities.shp");List<SimpleFeature> bufferResult = new ArrayList<>();SimpleFeatureIterator iterator = cities.features();SimpleFeatureType type = CommonMethod.createType(Polygon.class, "citesBuffer");SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(type);while (iterator.hasNext()) {SimpleFeature simpleFeature = iterator.next();Geometry geometry = (Geometry) simpleFeature.getDefaultGeometry();Geometry buffer = geometry.buffer(10);featureBuilder.add("1");featureBuilder.add(buffer);SimpleFeature bufferSimpleFeature = featureBuilder.buildFeature(null);bufferResult.add(bufferSimpleFeature);}SimpleFeatureCollection collection = new ListFeatureCollection(type, bufferResult);CommonMethod.createShp("E:\\data\\shp\\citiesBuffer.shp", collection);
}
- 合并同类图层
public static void union() throws IOException {SimpleFeatureCollection featureCollectionP1 = readFeatureCollection("E:\\data\\shp\\countries_part1.shp");SimpleFeatureCollection featureCollectionP2 = readFeatureCollection("E:\\data\\shp\\countries_part2.shp");List<SimpleFeature> features = new ArrayList<>();SimpleFeatureIterator iterator1 = featureCollectionP1.features();SimpleFeatureIterator iterator2 = featureCollectionP2.features();while (iterator1.hasNext()) {SimpleFeature simpleFeature = iterator1.next();features.add(simpleFeature);}while (iterator2.hasNext()) {SimpleFeature simpleFeature = iterator2.next();features.add(simpleFeature);}SimpleFeatureCollection collection = new ListFeatureCollection(featureCollectionP1.getSchema(), features);CommonMethod.createShp("E:\\data\\shp\\countries_union.shp", collection);}
- 多个多边形合并成一个
public static void merge() throws IOException {SimpleFeatureCollection collection = readFeatureCollection("E:\\data\\shp\\countries_mergedata.shp");SimpleFeatureIterator features = collection.features();List<Polygon> polygons = new ArrayList<>();while (features.hasNext()) {SimpleFeature simpleFeature = features.next();Geometry defaultGeometry = (Geometry) simpleFeature.getDefaultGeometry();Geometry union = defaultGeometry.union();polygons.add((Polygon) union);}Polygon[] ps = polygons.toArray(new Polygon[polygons.size()]);MultiPolygon multiPolygon = new MultiPolygon(ps, new GeometryFactory());Geometry union = multiPolygon.union();SimpleFeatureType type = CommonMethod.createType(MultiPolygon.class, "countriesMerge");SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type);builder.add("1");builder.add(union);SimpleFeature simpleFeature = builder.buildFeature(null);List<SimpleFeature> featureList = new ArrayList<>();featureList.add(simpleFeature);SimpleFeatureCollection simpleFeatureCollection = new ListFeatureCollection(type, featureList);// 生成矢量数据CommonMethod.createShp("E:\\data\\shp\\countriesMerge.shp", simpleFeatureCollection);
}
- 擦除
public static void erase() throws IOException {SimpleFeatureCollection subCollection = readFeatureCollection("E:\\data\\shp\\countries_differenceData.shp");SimpleFeatureCollection collection = readFeatureCollection("E:\\data\\shp\\countriesMerge.shp");SimpleFeatureIterator subFeatures = subCollection.features();SimpleFeatureIterator features = collection.features();Geometry subGeometry = null;while (subFeatures.hasNext()) {SimpleFeature simpleFeature = subFeatures.next();subGeometry = (Geometry) simpleFeature.getDefaultGeometry();}Geometry geometry = null;while (features.hasNext()) {SimpleFeature simpleFeature = features.next();geometry = (Geometry) simpleFeature.getDefaultGeometry();}Geometry difference = geometry.difference(subGeometry);SimpleFeatureType type = CommonMethod.createType(MultiPolygon.class, "countriesDifference");SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type);builder.add("1");builder.add(difference);SimpleFeature simpleFeature = builder.buildFeature(null);List<SimpleFeature> featureList = new ArrayList<>();featureList.add(simpleFeature);SimpleFeatureCollection simpleFeatureCollection = new ListFeatureCollection(type, featureList);// 生成矢量数据CommonMethod.createShp("E:\\data\\shp\\countriesDifference.shp", simpleFeatureCollection);
}
擦除中间部分,相当于减法
- 求交集
public static void intersect() throws IOException {SimpleFeatureCollection intersectCollection = readFeatureCollection("E:\\data\\shp\\countries_intersect.shp");SimpleFeatureCollection countries = readFeatureCollection("E:\\data\\shp\\countries.shp");SimpleFeatureIterator features = intersectCollection.features();SimpleFeatureIterator countriesFeatures = countries.features();List<Polygon> polygons = new ArrayList<Polygon>();Geometry other = null;while (features.hasNext()) {SimpleFeature next = features.next();other = (Geometry) next.getDefaultGeometry();other = other.buffer(0);}List<Geometry> geometries = new ArrayList<>();// 一个一个求交集,合并成一个大图层求交集会报错,还不清楚什么原因while (countriesFeatures.hasNext()) {SimpleFeature next = countriesFeatures.next();Geometry defaultGeometry = (Geometry) next.getDefaultGeometry();if (defaultGeometry instanceof MultiPolygon) {MultiPolygon multiPolygon = (MultiPolygon) defaultGeometry;int numGeometries = multiPolygon.getNumGeometries();for (int i = 0; i < numGeometries; i ++) {Geometry geometryN = multiPolygon.getGeometryN(i);boolean valid = geometryN.isValid();System.out.println("======>" + valid);polygons.add((Polygon) multiPolygon.getGeometryN(i));try {Geometry intersection = other.intersection(geometryN);geometries.add(intersection);} catch (Exception e) {Property fid = next.getProperty("FID");System.out.println(fid.getValue());}}} else {Geometry union = defaultGeometry.union();polygons.add((Polygon) union);}}SimpleFeatureType type = CommonMethod.createType(MultiPolygon.class, "countriesIntersection");SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type);List<SimpleFeature> featureList = new ArrayList<>();for (int i = 0; i < geometries.size(); i ++) {builder.add("1");builder.add(geometries.get(i));SimpleFeature simpleFeature = builder.buildFeature(null);featureList.add(simpleFeature);}SimpleFeatureCollection simpleFeatureCollection = new ListFeatureCollection(type, featureList);// 生成矢量数据CommonMethod.createShp("E:\\data\\shp\\countriesIntersection.shp", simpleFeatureCollection);
}
拓扑关系:
运行结果:
坐标参考
- 得到坐标系的方式
// 方式一得到WGS84
CoordinateReferenceSystem targetCRS = DefaultGeographicCRS.WGS84;
// 方式二
CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:4490");
坐标系EPSG查询网址:
https://epsg.io/
- 坐标转换
public static void projTransForm() throws FactoryException, IOException {File file = new File("E:\\data\\shp\\single.shp");FileDataStore store = FileDataStoreFinder.getDataStore(file);FeatureSource featureSource = store.getFeatureSource();SimpleFeatureType type = (SimpleFeatureType) featureSource.getSchema();// 源坐标CoordinateReferenceSystem sourceCRS = type.getCoordinateReferenceSystem();// 目标坐标CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:4490");// allow for some error due to different datumsboolean lenient = true;MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, lenient);// 重新写文件// 获取到要素集合SimpleFeatureCollection featureCollection = (SimpleFeatureCollection) featureSource.getFeatures();ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();Map<String, Serializable> create = new HashMap<>();File newShpFile = new File("E:\\data\\shp\\resingle.shp");create.put("url", newShpFile.toURI().toURL());create.put("create spatial index", Boolean.TRUE);DataStore dataStore = dataStoreFactory.createNewDataStore(create);SimpleFeatureType featureType = SimpleFeatureTypeBuilder.retype(type, targetCRS);dataStore.createSchema(featureType);// Get the name of the new Shapefile, which will be used to open the FeatureWriterString createdName = dataStore.getTypeNames()[0];Transaction transaction = new DefaultTransaction("Reproject");try (FeatureWriter<SimpleFeatureType, SimpleFeature> writer =dataStore.getFeatureWriterAppend(createdName, transaction);SimpleFeatureIterator iterator = featureCollection.features()) {while (iterator.hasNext()) {// copy the contents of each feature and transform the geometrySimpleFeature feature = iterator.next();SimpleFeature copy = writer.next();copy.setAttributes(feature.getAttributes());Geometry geometry = (Geometry) feature.getDefaultGeometry();Geometry geometry2 = JTS.transform(geometry, transform);copy.setDefaultGeometry(geometry2);writer.write();}transaction.commit();writer.close();} catch (Exception problem) {problem.printStackTrace();transaction.rollback();} finally {transaction.close();}
}
导出地图
主要使用的是MapContent + sld样式,但是有个致命的问题就是图例和标题用它非常难以实现,还需要进一步研究。
在生成地图之前我们先介绍一下SLD样式,它是一个XML文件定义了图层渲染的方式。一个完整的SLD文件如下:
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<sld:StyledLayerDescriptor version="1.0.0" xmlns:sld="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xlink="http://www.w3.org/1999/xlink"><sld:NamedLayer><sld:Name>countries</sld:Name><sld:UserStyle><sld:Name>Style1</sld:Name><sld:FeatureTypeStyle><sld:FeatureTypeName>countries</sld:FeatureTypeName><sld:Rule><sld:Name>countries</sld:Name><sld:Title>countries</sld:Title><sld:PolygonSymbolizer><sld:Fill><sld:CssParameter name="fill">#97DBF2</sld:CssParameter><sld:CssParameter name="fill-opacity">1</sld:CssParameter></sld:Fill></sld:PolygonSymbolizer><sld:TextSymbolizer><sld:Label><ogc:PropertyName>LONG_NAME</ogc:PropertyName></sld:Label><sld:Font><sld:CssParameter name="font-family">????</sld:CssParameter><sld:CssParameter name="font-family">0</sld:CssParameter><sld:CssParameter name="font-size">14</sld:CssParameter><sld:CssParameter name="font-style">normal</sld:CssParameter><sld:CssParameter name="font-weight">normal</sld:CssParameter></sld:Font><sld:Fill><sld:CssParameter name="fill">#000000</sld:CssParameter><sld:CssParameter name="fill-opacity">1.0</sld:CssParameter></sld:Fill></sld:TextSymbolizer></sld:Rule></sld:FeatureTypeStyle></sld:UserStyle></sld:NamedLayer>
</sld:StyledLayerDescriptor>
大致解释一下:
sld:Name 定义了图层的名字
sld:PolygonSymbolizer 定义面要素的填充方式颜色等,如果是点图层则是sld:PointSymbolizer, 如果线要素sld:LineSymbolizer.
sld:sld:TextSymbolizer 定义了文本的样式
读取样式:
StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory();
SLDParser stylereader = new SLDParser(styleFactory, sldFile.toURI().toURL());
Style[] stylearray = stylereader.readXML();
更多样式相关:
https://docs.geotools.org/latest/userguide/library/render/style.html
其实我们不用手写这些样式文件,如果样式多手写效率非常低,我们可以先在Arcgis中调出想要的地图样式,在借助插件ArcGIS_SLD_Converter就可以导出样式。
使用方式:https://blog.csdn.net/qq_43259860/article/details/124429280
public static MapContent map = new MapContent();
// 添加图层
public static void addShapeLayer(String shpPath, String sldPath){try{File file = new File(shpPath);ShapefileDataStore shpDataStore = null;shpDataStore = new ShapefileDataStore(file.toURI().toURL());//设置编码Charset charset = Charset.forName("GB18030");shpDataStore.setCharset(charset);String typeName = shpDataStore.getTypeNames()[0];SimpleFeatureSource featureSource = null;featureSource = shpDataStore.getFeatureSource (typeName);//SLD的方式File sldFile = new File(sldPath);StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory();SLDParser stylereader = new SLDParser(styleFactory, sldFile.toURI().toURL());Style[] stylearray = stylereader.readXML();Style style = stylearray[0];Layer layer = new FeatureLayer(featureSource, style);map.addLayer(layer);}catch(Exception e){e.printStackTrace();}
}
// 导出地图
public static void getMapContent(Map paras, String imgPath){try{double[] bbox = (double[]) paras.get("bbox");double x1 = bbox[0], y1 = bbox[1], x2 = bbox[2], y2 = bbox[3];int width = (int) paras.get("width"), height=(int) paras.get("height");// 设置输出范围CoordinateReferenceSystem crs = DefaultGeographicCRS.WGS84;ReferencedEnvelope mapArea = new ReferencedEnvelope(x1, x2, y1, y2, crs);// 初始化渲染器StreamingRenderer sr = new StreamingRenderer();sr.setMapContent(map);// 初始化输出图像BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);Graphics g = bi.getGraphics();((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);Rectangle rect = new Rectangle(0, 0, width, height);// 绘制地图sr.paint((Graphics2D) g, rect, mapArea);//将BufferedImage变量写入文件中。ImageIO.write(bi,"png",new File(imgPath));}catch(Exception e){e.printStackTrace();}
}
运行结果:
GP 工具
geotools封装了不少可以直接拿来用的工具,矢量和栅格是分开的还是比较好用,值得去探索一番。方法里面的注释也非常详细,每个参数都加了相应的描述。我前面列举的常见的空间分析都可以通过gp类来实现。
下面举一些简单的例子,由于是例子代码写的不是很规范, 大概示范一些这些API怎么用,栅格的gp工具我就不举例子了大概都一样几句代码就能搞定:
// gp 裁剪
public static void clip() throws IOException, ParseException {SimpleFeatureCollection featureCollection = CommonMethod.readFeatureCollection("E:\\data\\bf.shp");WKTReader wktReader = new WKTReader();Geometry geometry = wktReader.read("MultiPolygon (((120.06170797626219837 30.54693549152121079, 120.12450974119508373 30.54442342092389495, 120.13644207653233309 30.49192114543999921, 120.06723453157628967 30.48878105719335707, 120.06170797626219837 30.54693549152121079)))");ClippedFeatureCollection clippedFeatureCollection = new ClippedFeatureCollection(featureCollection,geometry, false);CommonMethod.createShp("E:\\data\\clip.shp",clippedFeatureCollection);
}
// gp 缓冲区
public static void buffer() throws IOException {BufferFeatureCollection bf = new BufferFeatureCollection();SimpleFeatureCollection featureCollection = CommonMethod.readFeatureCollection("E:\\data\\bf.shp");SimpleFeatureCollection simpleFeatureCollection = bf.execute(featureCollection, 000.1d, "czzb");FileUtil.del("E:\\data\\afterBuffer.shp");CommonMethod.createShp("E:\\data\\afterBuffer.shp",simpleFeatureCollection);
}
// gp 合并图层
public static void union() throws ClassNotFoundException, IOException {UnionFeatureCollection unionFeatureCollection = new UnionFeatureCollection();SimpleFeatureCollection first = CommonMethod.readFeatureCollection("E:\\data\\bf.shp");SimpleFeatureCollection second = CommonMethod.readFeatureCollection("E:\\data\\hardData.shp");SimpleFeatureCollection result = unionFeatureCollection.execute(first, second);CommonMethod.createShp("E:\\data\\union.shp", result);
}
// gp 分组聚合属性 这个非常好用
public static void aggregate() throws IOException {AggregateProcess aggregateProcess = new AggregateProcess();SimpleFeatureCollection first = CommonMethod.readFeatureCollection("E:\\data\\bf.shp");Set<AggregateProcess.AggregationFunction> functions = new HashSet<>();// 要统计的类型functions.add(AggregateProcess.AggregationFunction.Average);functions.add(AggregateProcess.AggregationFunction.Sum);functions.add(AggregateProcess.AggregationFunction.Max);// 分组字段列表List<String> groupFields = new ArrayList<>();groupFields.add("dlmc");groupFields.add("zldwdm");AggregateProcess.Results execute = aggregateProcess.execute(first,"shape_leng", functions, false,groupFields,new NullProgressListener());
}
// gp 简化几何, 它的源码使用道格拉斯算法实现的
public static void simplify() throws Exception {SimplifyProcess simplifyProcess = new SimplifyProcess();SimpleFeatureCollection simpleFeatureCollection = CommonMethod.readFeatureCollection("E:\\data\\hardData.shp");SimpleFeatureCollection result = simplifyProcess.execute(simpleFeatureCollection, 0.00001, true);CommonMethod.createShp("E:\\data\\simpfy.shp", result);
}
// gp 投影变换
public void reproject() {ReprojectProcess reprojectProcess = new ReprojectProcess();SimpleFeatureCollection simpleFeatureCollection = CommonMethod.readFeatureCollection("E:\\data\\hardData.shp");SimpleFeatureCollection projectCollection = reprojectProcess.execute(simpleFeatureCollection, CRS.decode("EPSG:4490", true), CRS.decode("EPSG:4549", true));CommonMethod.createShp("E:\\data\\simpfy.shp", projectCollection);
}
// gp 相交
public static void intersect() throws ClassNotFoundException, IOException {IntersectionFeatureCollection intersectionFeatureCollection = new IntersectionFeatureCollection();SimpleFeatureCollection first = CommonMethod.readFeatureCollection("E:\\data\\bf.shp");SimpleFeatureCollection second = CommonMethod.readFeatureCollection("E:\\data\\hardData.shp");// 要保留的字段,我这里为了方便都是用的同一份字段List<String> fretain = new ArrayList<>();fretain.add("id");fretain.add("mj");fretain.add("dlmc");SimpleFeatureCollection result = intersectionFeatureCollection.execute(first, second,fretain, fretain, IntersectionFeatureCollection.IntersectionMode.INTERSECTION,true, true);CommonMethod.createShp("E:\\data\\intersect.shp", result);}
下面我列举一下可能会遇到的问题:
1)如果你的图形非常复杂,数据是别人提供的,再运行GP类的时候可能会报拓扑错误,这个时候你可以采用buffer(0)来修复几何的拓扑。
2)在坐标转换的时候如果发生下图警告:
设置一些decode的第二个参数就能解决,千万不能不管这个警告不然转换出来的数据是有问题的。
在我没有设置第二个参数的情况下,我坐标转换出来的结果被旋转了90度,设置第二个参数为true就可以解决这个问题。
补充公共方法
因为有朋友需要,在这里补充一下CommonMethod
说明一下,里面构造要素用的是最简单的方法直接写死的,就是为了方便测试功能,需要按自己的需求改一改。
package com.example.gis.utils;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.FeatureSource;
import org.geotools.data.Transaction;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.opengis.feature.simple.SimpleFeatureType;
import java.io.*;
import java.net.MalformedURLException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
public class CommonMethod {public static SimpleFeatureType createType(Class<?> c, String layerName) {SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();builder.setCRS(DefaultGeographicCRS.WGS84);builder.add("FID",String.class);builder.add("the_geom", c);// 设置了图层的名字builder.setName(layerName);SimpleFeatureType simpleFeatureType = builder.buildFeatureType();return simpleFeatureType;}public static void createShp(String shpPath, SimpleFeatureCollection collection) throws IOException {File shpFile = new File(shpPath);ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();SimpleFeatureType simpleFeatureType = collection.getSchema();// 创造shpstore需要的参数Map<String, Serializable> params = new HashMap<>();params.put("url", shpFile.toURI().toURL());params.put("create spatial index", Boolean.TRUE);ShapefileDataStore newDataStore =(ShapefileDataStore) dataStoreFactory.createNewDataStore(params);newDataStore.createSchema(simpleFeatureType);Transaction transaction = new DefaultTransaction("create");String typeName = newDataStore.getTypeNames()[0];SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;featureStore.setTransaction(transaction);featureStore.addFeatures(collection);featureStore.setTransaction(transaction);transaction.commit();transaction.close();}public static SimpleFeatureCollection readFeatureCollection(String shpPath) {SimpleFeatureCollection featureCollection = null;File shpFile = new File(shpPath);try {ShapefileDataStore shapefileDataStore = new ShapefileDataStore(shpFile.toURI().toURL());// 设置编码,防止属性的中文字符出现乱码shapefileDataStore.setCharset(Charset.forName("UTF-8"));// 这个typeNamae不传递,默认是文件名称FeatureSource featuresource = shapefileDataStore.getFeatureSource(shapefileDataStore.getTypeNames()[0]);featureCollection = (SimpleFeatureCollection) featuresource.getFeatures();} catch (MalformedURLException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return featureCollection;}
}