Geotools操作GeoJSON:解析FeatureCollection对象文件
Geotools操作GeoJSON:解析FeatureCollection对象文件
GeoJSON是基于JavaScript的对象的地理信息数据格式。
GeoJSON格式示例:
{
"type":"FeatureCollection",
"features":[
{
"type":"Feature",
"properties":{
"area": 3865207830,
"text": null
},
"id":"polygon.1",
"geometry":{
"type":"Polygon",
"coordinates":[
[
[
116.19827270507814,
39.78321267821705
],
[
116.04446411132814,
39.232253141714914
],
[
116.89590454101562,
39.3831409542565
],
[
116.86981201171876,
39.918162846609455
],
[
116.19827270507814,
39.78321267821705
]
]
]
}
}
],
"crs":{
"type":"name",
"properties":{
"name":"EPSG:4326"
}
}
}
一、解析FeatureCollection对象文件
一个FeatureCollection对象文本,包含一个Feature要素。
1.1 geotools操作GeoJSON过程中的问题及相关源码(转载自:Shanks7529)
public static void main(String[] a) throws Exception {
// 坐标顺序是EAST_NORTH,即经度在前
String json = "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"area\":3865207830, \"text\": null},\"id\":\"polygon.1\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[116.19827270507814,39.78321267821705],[116.04446411132814,39.232253141714914],[116.89590454101562,39.3831409542565],[116.86981201171876,39.918162846609455],[116.19827270507814,39.78321267821705]]]}}],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}";
// 指定GeometryJSON构造器,15位小数
FeatureJSON fjson_15 = new FeatureJSON(new GeometryJSON(15));
// 读取为FeatureCollection
FeatureCollection featureCollection = fjson_15.readFeatureCollection(json);
// 获取SimpleFeatureType
SimpleFeatureType simpleFeatureType = (SimpleFeatureType) featureCollection.getSchema();
// 第1个问题。坐标顺序与实际坐标顺序不符合
System.out.println(CRS.getAxisOrder(simpleFeatureType.getCoordinateReferenceSystem())); //输出:NORTH_EAST
//第2个问题。查看空间列名称
System.out.println(simpleFeatureType.getGeometryDescriptor().getLocalName()); //输出:geometry
//第3个问题。坐标精度丢失
//第4个问题。默认无坐标系和空值输出
OutputStream ostream = new ByteArrayOutputStream();
GeoJSON.write(featureCollection, ostream);
// 输出:{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[116.1983,39.7832],[116.0445,39.2323],[116.8959,39.3831],[116.8698,39.9182],[116.1983,39.7832]]]},"properties":{"area":3865207830},"id":"polygon.1"}]}
System.out.println(ostream);
// 第5个问题。坐标变换问题,由坐标顺序引发
SimpleFeatureIterator iterator = (SimpleFeatureIterator) featureCollection.features();
SimpleFeature simpleFeature = iterator.next();
Geometry geom = (Geometry) simpleFeature.getDefaultGeometry();
iterator.close();
System.out.println(geom.getArea()); // 输出:0.4043554020447081
MathTransform transform_1 = CRS.findMathTransform(CRS.decode("EPSG:4326"), CRS.decode("EPSG:3857"),true);
// 下面一行代码会报异常:Exception in thread "main" org.geotools.referencing.operation.projection.ProjectionException: Latitude 116°11.8'N is too close to a pole.
/*Geometry geom_3857 = JTS.transform(geom, transform_1);
System.out.println(geom_3857.getArea());*/
}
上述事例代码给出了将GeoJSON解析成FeatureCollection时出现的一些问题。 第1个问题是得到的FeatureCollection坐标顺序是错误的,给出的GeoJSON坐标顺序是经度(EAST)在前,geotools读取时给出了默认的坐标顺序(纬度在前),看下面的org.geotools.geojson.feature.FeatureJSON的readFeatureCollection(Object input)方法源码:
// org.geotools.geojson.feature.FeatureJSON
// input可以是File,Reader,InputStream等
public FeatureCollection readFeatureCollection(Object input) throws IOException {
// 新建一个DefaultFeatureCollection对象,
DefaultFeatureCollection features = new DefaultFeatureCollection(null, null);
// FeatureCollectionIterator实现了FeatureIterator接口,是一个内部类,用于控制从geojson文本中读取要素和坐标系等信息。
FeatureCollectionIterator it = (FeatureCollectionIterator) streamFeatureCollection(input);
while(it.hasNext()) {
features.add(it.next());
}
if (features.getSchema() != null
&& features.getSchema().getCoordinateReferenceSystem() == null
&& it.getHandler().getCRS() != null ) {
try {
// 只将坐标系信息写入,即只更改了坐标系
return new ForceCoordinateSystemFeatureResults(features, it.getHandler().getCRS());
} catch (SchemaException e) {
throw (IOException) new IOException().initCause(e);
}
}
return features;
}
ForceCoordinateSystemFeatureResults是FeatureCollection接口的一个子类,直接更改坐标系信息,原数据中的坐标信息不变。坐标系的生成是在org.geotools.geojson.feature.CRSHandler类中,相关代码如下:
//org.geotools.geojson.feature.CRSHandler
public boolean primitive(Object value) throws ParseException, IOException {
if (state == 2) {
try {
try {
crs = CRS.decode(value.toString()); //坐标顺序默认NORTH_EAST,与实际数据不符
}
catch(NoSuchAuthorityCodeException e) {
//try pending on EPSG
try {
crs = CRS.decode("EPSG:" + value.toString());
}
catch(Exception e1) {
//throw the original
throw e;
}
}
}
catch(Exception e) {
throw (IOException) new IOException("Error parsing " + value + " as crs id").initCause(e);
}
state = -1;
}
return true;
}
第1个问题可做如下修改,使坐标系正常:
String srs = CRS.lookupIdentifier(simpleFeatureType.getCoordinateReferenceSystem(),true); // 获取EPSG
featureCollection = new ForceCoordinateSystemFeatureResults(featureCollection, CRS.decode(srs, true));
第2个问题,空间列名称不是我们想要的"the_geom",而是"geometry",这使和另外一些数据源的FeatureCollection一起做操作时空间列不一致。相关代码在org.geotools.geojson.feature.FeatureHandler类中:
//org.geotools.geojson.feature.FeatureHandler
void addGeometryType(SimpleFeatureTypeBuilder typeBuilder, Geometry geometry) {
// 空间列名"geometry",而不是"the_geom"
typeBuilder.add("geometry", geometry != null ? geometry.getClass() : Geometry.class);
typeBuilder.setDefaultGeometry("geometry");
}
SimpleFeatureTypeBuilder类用于构建SimpleFeatureType,SimpleFeatureType描述了FeatureCollection对象属性、数据类型、坐标系等信息。第2个问题可做如下修改,使空间列变为"the_geom":
// 构建新的SimpleFeatureType
public static SimpleFeatureType retype(SimpleFeatureType oldType){
SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder();
typeBuilder.init(oldType);
// the_geom
if("geometry".equals(oldType.getGeometryDescriptor().getLocalName())){
typeBuilder.remove("geometry");
typeBuilder.add("the_geom",oldType.getType("geometry").getBinding());
}
//生成新的SimpleFeatureType
return typeBuilder.buildFeatureType();
}
// 新建一个方法,用于变换feature的type
public static SimpleFeature retypeFeature(SimpleFeature feature,SimpleFeatureType newType) {
SimpleFeatureBuilder builder = new SimpleFeatureBuilder(newType);
// 遍历属性
for (AttributeDescriptor att : newType.getAttributeDescriptors()) {
Object value = feature.getAttribute(att.getName());
// 空间列
if(Geometry.class.isAssignableFrom(att.getType().getBinding())){
builder.set("the_geom", feature.getDefaultGeometry());
continue;
}
builder.set(att.getName(), value);
}
return builder.buildFeature(feature.getID());
}
在测试代码中加入如下,得到最终的FeatureCollection:
SimpleFeatureType newType = retype(simpleFeatureType);
// ListFeatureCollection是FeatureCollection的一个子类
ListFeatureCollection listFeatureCollection = new ListFeatureCollection(newType);
SimpleFeatureIterator iterator_3 = (SimpleFeatureIterator) featureCollection.features();
while (iterator_3.hasNext()){
SimpleFeature newFeature = retypeFeature(iterator_3.next(),newType);
listFeatureCollection.add(newFeature);
}
iterator_3.close();
第3(坐标精度丢失)、第4(默认无坐标系和空值输出)、第5(由坐标顺序引发坐标变换)这三个问题。我用GeoJSON的static void write(Object obj, Object output)静态方法将FeatureCollection转化成了json文本输出,先看org.geotools.geojson.GeoJSON源码:
// 该类用于FeatureCollection、Feature和坐标系的JSON输出
public class GeoJSON {
static GeometryJSON gjson = new GeometryJSON();
static FeatureJSON fjson = new FeatureJSON(); // 用的默认构造器
public static Object read(Object input) throws IOException {
throw new UnsupportedOperationException();
}
public static void write(Object obj, Object output) throws IOException {
if (obj instanceof Geometry) {
gjson.write((Geometry)obj, output);
}
else if (obj instanceof Feature || obj instanceof FeatureCollection ||
obj instanceof CoordinateReferenceSystem) {
if (obj instanceof SimpleFeature) {
fjson.writeFeature((SimpleFeature)obj, output);
}
else if (obj instanceof FeatureCollection) {
fjson.writeFeatureCollection((FeatureCollection)obj, output);
}
else if (obj instanceof CoordinateReferenceSystem) {
fjson.writeCRS((CoordinateReferenceSystem)obj, output);
}
else {
throw new IllegalArgumentException("Unable able to encode object of type " + obj.getClass());
}
}
}
}
该类除写Geometry外都是调用FeatureJSON的方法,在看下FeatureJSON的构造器和实例变量:
// org.geotools.geojson.feature.FeatureJSON
GeometryJSON gjson; // 决定坐标保留的位数
SimpleFeatureType featureType;
AttributeIO attio;
boolean encodeFeatureBounds = false; // true表示json文本中Feature输出bbox
boolean encodeFeatureCollectionBounds = false; // true表示json文本中FeatureCollection输出bbox
boolean encodeFeatureCRS = false; // true表示json文本中Feature输出坐标系
boolean encodeFeatureCollectionCRS = false; // true表示json文本中FeatureCollection输出坐标系
boolean encodeNullValues = false; // true表示识别值为null的属性
public FeatureJSON() {
this(new GeometryJSON()); // GeometryJSON默认保留4为小数
}
public FeatureJSON(GeometryJSON gjson) { // 自定义GeometryJSON,可控制小数位数
this.gjson = gjson;
attio = new DefaultAttributeIO();
}
GeometryJSON的相关代码就不列出来了。解决第3(坐标精度丢失)、第4(默认无坐标系和空值输出)问题我们只需做一些设置。如果想统一用GeoJSON.write()方法写json文本,可以重写该类,设置精度,代码如下:
// 重写后的GeoJSON
public class GeoJSON {
static GeometryJSON gjson = new GeometryJSON(15); // 15位小数
static FeatureJSON fjson = new FeatureJSON(gjson); // 指定GeometryJSON
public static Object read(Object input) throws IOException {
throw new UnsupportedOperationException();
}
public static void write(Object obj, Object output) throws IOException {
if (obj instanceof Geometry) {
gjson.write((Geometry)obj, output);
}
else if (obj instanceof Feature || obj instanceof FeatureCollection ||
obj instanceof CoordinateReferenceSystem) {
// 值为null的属性也识别
fjson.setEncodeNullValues(true);
// 输出坐标系文本
fjson.setEncodeFeatureCollectionCRS(true);
if (obj instanceof SimpleFeature) {
fjson.writeFeature((SimpleFeature)obj, output);
}
else if (obj instanceof FeatureCollection) {
fjson.writeFeatureCollection((FeatureCollection)obj, output);
}
else if (obj instanceof CoordinateReferenceSystem) {
fjson.writeCRS((CoordinateReferenceSystem)obj, output);
}
else {
throw new IllegalArgumentException("Unable able to encode object of type " + obj.getClass());
}
}
}
}
如果就想用FeatureJSON操作输出,可以在测试代码中添加如下代码解决:
// fjson_15已经保留15位
fjson_15.setEncodeFeatureCollectionCRS(true);
fjson_15.setEncodeNullValues(true);
fjson_15.writeFeatureCollection(featureCollection,System.out); // 控制台输出和原始geojson一致
针对第5(由坐标顺序引发坐标变换)个问题,“org.geotools.referencing.operation.projection.ProjectionException: Latitude 116°11.8’N is too close to a pole”异常其实是由坐标顺序不正确导致,经纬度顺序调换后识别的坐标超出了范围,不是当前坐标系能表示的值了。这是一个隐藏问题,在处理另一些原数据或变换不同的坐标系时,不一定会产生这个异常,那用不合理的坐标顺序得到的结果是不正确的。调整代码如下即可以解决:
String srs = CRS.lookupIdentifier(simpleFeatureType.getCoordinateReferenceSystem(),true);
// CRS.decode()方法可以设置经纬度顺序
MathTransform transform_2 = CRS.findMathTransform(CRS.decode(srs,true), CRS.decode("EPSG:3857",true),true);
Geometry geom_3857 = JTS.transform(geom, transform_2);
System.out.println(geom_3857.getArea()); // 输出:6.501222710260582E9
测试代码里输出了几何对象geom的面积,但这个面积很粗糙,只做测试用。给出的GeoJSON文本中的"area"值也只做参考,不是最精确的面积。大家都知道,EPSG:3857以EPSG:4326地理坐标系和投影方式为伪墨卡托的平面坐标系,给出的面积偏差较大。
原创作者:Shanks7529
文章链接:geotools操作GeoJSON过程中的问题及相关源码
1.2 方法二:读取本地txt文件进行解析
FeatJson.class
import com.geomesa.spark.SparkJTS.Operation;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.operation.distance.DistanceOp;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.feature.FeatureCollection;
import org.geotools.geojson.GeoJSON;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.geojson.geom.GeometryJSON;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import java.io.*;
public class FeatJson {
static GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory(null);
public static void main(String[] args) throws IOException {
//读取本地文件
FileReader reader = new FileReader("D:/GitProjects/GeoMesa/GeoMesaSpark/src/main/resources/gsmc.txt");
BufferedReader bufferReader = new BufferedReader(reader);
String dict = bufferReader.readLine();
//按行读取文件
//构造FeatureJSON对象,GeometryJSON保留15位小数
FeatureJSON featureJSON = new FeatureJSON(new GeometryJSON(15));
FeatureCollection featureCollection = featureJSON.readFeatureCollection(dict);
SimpleFeatureType simpleFeatureType = (SimpleFeatureType) featureCollection.getSchema();
System.out.println(simpleFeatureType.getGeometryDescriptor().getLocalName());
OutputStream ostream = new ByteArrayOutputStream();
GeoJSON.write(featureCollection, ostream);
System.out.println(ostream);
SimpleFeatureIterator iterator = (SimpleFeatureIterator) featureCollection.features();
SimpleFeature simpleFeature = iterator.next();
Geometry geom = (Geometry) simpleFeature.getDefaultGeometry();
iterator.close();
System.out.println(geom.getLength());
System.out.println(geom.getCoordinate());
System.out.println(geom.getBoundary());
System.out.println(geom.getGeometryType());
//新建一个经纬度坐标对象
Coordinate coordinate1 = new Coordinate(1.357846020181606E7, 4505819.87283728);
Coordinate[] coordinates2 = geom.getCoordinates();
Operation op = new Operation();
//求点到线的距离
System.out.println("距离:"+op.distanceGeo(geometryFactory.createPoint(coordinate1),geometryFactory.createLineString(coordinates2)));
//求点到线的最近一个点
System.out.println(DistanceOp.nearestPoints(geometryFactory.createPoint(coordinate1),geometryFactory.createLineString(coordinates2)));
bufferReader.close();
reader.close();
}
}
二、Java Code Examples
Java Code Examples for org.geotools.data.simple.simplefeaturecollection.features()
三、API
Geotools操作GeoJSON:解析FeatureCollection对象文件的更多相关文章
- JavaScript使用浏览器内置XML解析器解析DOM对象
所有现代浏览器都内建了供读取和操作 XML 的 XML 解析器.解析器把 XML 转换为 XML DOM 对象 (可通过 JavaScript 操作的对象). 一.获取DOM对象 XMLHttpReq ...
- 构建工具是如何用 node 操作 html/js/css/md 文件的
构建工具是如何用 node 操作 html/js/css/md 文件的 从本质上来说,html/js/css/md ... 源代码文件都是文本文件,文本文件的内容都是字符串,对文本文件的操作其实就是对 ...
- ARM 之一 ELF文件、镜像(Image)文件、可执行文件、对象文件 详解
[转]https://blog.csdn.net/ZCShouCSDN/article/details/100048461 ELF 文件规范 ELF(Executable and Linking ...
- 14. 深入解析Pod对象(一)
14. 深入解析Pod对象(一) """ 通过前面的讲解,大家应该都知道: Pod,而不是容器,它是 Kubernetes 项目中的最小编排单位.将这个设计落实到 API ...
- 15. 深入解析Pod对象(二):使用进阶
15. 深入解析Pod对象(二):使用进阶 15.1 Projected Volume,投射数据卷 备注:Projected Volume 是 Kubernetes v1.11 之后的新特性 在 Ku ...
- mybatis源码配置文件解析之五:解析mappers标签(解析XML映射文件)
在上篇文章中分析了mybatis解析<mappers>标签,<mybatis源码配置文件解析之五:解析mappers标签>重点分析了如何解析<mappers>标签中 ...
- c# .Net :Excel NPOI导入导出操作教程之读取Excel文件信息及输出
c# .Net :Excel NPOI导入导出操作教程之读取Excel文件信息及输出 using NPOI.HSSF.UserModel;using NPOI.SS.UserModel;using S ...
- Java使用正则表达式解析LRC歌词文件
LRC歌词是一种应用广泛的歌词文件,各主流播放器都支持. lrc歌词文本中含有两类标签: 1.标识标签(ID-tags) [ar:艺人名] [ti:曲名] [al:专辑名] [by:编者(指编辑LRC ...
- 全面解析Linux数字文件权限
全面解析Linux数字文件权限 来源: 时间:2013-09-04 20:35:13 阅读数:11433 分享到:0 [导读] 在刚开始接触Linux时对于文件权限的理解并不是很透彻,这里详细 ...
随机推荐
- github与svn的区别
github与svn都属于版本控件系统,但是两者不同于,github是分布式的,svn不是分布的是属于集中式的. 1) 最核心的区别Git是分布式的,而Svn不是分布的.能理解这点,上手会很容 ...
- 别再费劲去找后台的前端框架了,2021 年就用 Fantastic-admin 吧
前言 你知道光是基于 Vue 的后台框架在 Github 上有多少个仓库么? 如果你搜索 vue admin 会得到 13120 个仓库,如果用 vue 后台 会得到 7596 个仓库,如果把两者结合 ...
- application.properties 中文乱码问题解决
1. 设置 File Encodings的Transparent native-to-ascii conversion为true,具体步骤如下:依次点击 File -> Settings -&g ...
- 一文教你轻松搞定ANR异常捕获与分析方法
1. ANR 产生原理 关于 ANR 的触发原因,Android 官方开发者文档中 "What Triggers ANR?" 有介绍,如下: Generally, the syst ...
- 建立索引和创建视图(结合YGGL.sql)
一.请按要求对YGGL库建立相关索引 (1)使用create index 语句创建索引 1.对employees表中的员工部门号创建普通索引depart_ind. mysql> create i ...
- aix5.3安装httpd服务
1.安装gcc(1)从IBM上下载 gcc-4.0.0-1.aix5.3.ppc.rpm gcc-cplusplus-4.0.0-1.aix5.3.ppc.rpm libgcc-4.0.0-1.aix ...
- halcon案例学习之cbm_label_simple
*cbm_label_simple 程序说明:*这个示例程序展示了如何使用基于组件的匹配来定位复合对象.在这种情况下,应该在图像中找到一个标签,用户既不知道其中的组件,也不知道它们之间的关系.因此,创 ...
- day123:MoFang:直播间列表信息的前后端实现&创建房间的前后端实现
目录 1.服务端提供所有直播间的列表信息 2.前端显示房间列表 3.创建房间 1.服务端提供所有直播间的列表信息 1.marshmallow.py from marshmallow_sqlalchem ...
- Es5数组新增的方法及用法
1.forEachforEach是Array新方法中最基本的一个,就是遍历,循环.例如下面这个例子: [1, 2 ,3, 4].forEach(alert);等同于下面这个传统的for循环: var ...
- DTCC 2020 | 阿里云李飞飞:云原生分布式数据库与数据仓库系统点亮数据上云之路
简介: 数据库将面临怎样的变革?云原生数据库与数据仓库有哪些独特优势?在日前的 DTCC 2020大会上,阿里巴巴集团副总裁.阿里云数据库产品事业部总裁.ACM杰出科学家李飞飞就<云原生分布式数 ...