前言

一个多月没有写博客了,今天尝试着动笔写点。

原因很多,最重要的原因是我转行了。是的,我离开了开发岗位,走向了开发的天敌-产品经理。虽然名义上是产品经理,但是干的事情也很杂,除了不写代码,其他的都干,经常还要加个小班,所以就没那么多时间研究技术上的东西,机械键盘上已经落下了一层薄薄的灰尘。但是自己确实又爱码农这一行,上班看着同事畅快的敲着代码,心里就有点痒,所以下班没事仍旧自己瞎捉摸,这不就总结出来今天这篇文章。

关于产品经理和研发的关系我还真得多说一句,虽然二者是天敌,但是一个懂研发的产品经理很容易和研发打交道,相互之间有很多的共同话题,每次项目来的时候我脑海中大概知道如何解决这件事情,甚至我还要帮项目对接的公司解决开发上的问题。。。

话不多说,开始今天的主题,今天主要介绍如何将 Shp 文件转为 GeoJson,这在 QGIS、ArcGIS 等专业软件中很容易实现,只需要点个按钮就行了,本文正是来研究这点个按钮背后发生的故事。本文是在使用 GeoTrellis 中碰到的,所以仍旧归入此博客集中,当然其中的框架等也都是基于 GeoTrellis 的。

一、实现方式

1.1 理论分析

其实这个过程逻辑上比较简单,首先将 Shp 文件读入内存,再分别读出空间属性和普通属性,将二者组合起来按照 GeoJson 文件的格式写入即可。

1.2 具体实现

  1. 读 Shp 文件

只需要一行代码即可解决:

val datas = ShapeFileReader.readSimpleFeatures(path)

这是 GeoTrellis 封装好的读 Shp文件的方法,但是此种方式存在一个问题,无法设置读 Shp 文件时的编码方式,如果 Shp 文件不是 UTF-8 编码会存在乱码的问题,简单改造一下源码即可实现:

val datas = {
  val file = new File(path)
  val shpDataStore = new ShapefileDataStore(path.getFileUrl())
  shpDataStore.setCharset(Charset.forName(charsetName))
  val ftItr = shpDataStore.getFeatureSource.getFeatures.features
  try {
    val simpleFeatures = mutable.ListBuffer[SimpleFeature]()
    while (ftItr.hasNext) simpleFeatures += ftItr.next()
    simpleFeatures
  } finally {
    ftItr.close
    shpDataStore.dispose()
  }

这样就能够读出 Shp 文件中的所有内容,空间属性附带普通属性,最终是 SimpleFeature 对象的集合。

  1. 将内容转为 Feature

所谓 Feature 其实就是空间属性和普通属性的结合。

  def parseAttribute(sf: SimpleFeature) = {
    import scala.collection.JavaConversions._
    sf.getProperties.drop(1).map { p =>
      val attr = sf.getAttribute(p.getName)
      p.getName.toString -> (if (attr == null) "" else attr.toString)
    }.toMap
  }

  def getGeometryFromSimpleFeature(sf: SimpleFeature) = {
    val original = sf.getDefaultGeometryProperty.getValue
    Geometry(original.asInstanceOf[com.vividsolutions.jts.geom.Geometry])
  }

  def toFeature(sf: SimpleFeature) = {
    Feature(getGeometryFromSimpleFeature(sf), parseAttribute(sf))
  }

以上方法能将单个 SimpleFeature 对象转为 Feature 对象,整个集合只需要实现一下 map 方法即可。

  1. 转为 GeoJson

在上一步中,肯定有同学很好奇,为什么要将从 Shp 中读出的 SimpleFeature 对象转为 Feature 对象,原因就在于有了 Feature 对象,我们就可以很简单的将其转为 GeoJson。GeoTrellis 内置了一个将 Feature 集合转为 GeoJson 的隐式方法,如下:

implicit class FeaturesToGeoJson[G <: Geometry, D: JsonWriter](features: Traversable[Feature[G, D]]) {
    def toGeoJson(): String = {
      JsonFeatureCollection(features).toJson.compactPrint
    }
  }

所以可以直接将第二步中得到的 Feature 集合转为 GeoJson,如下:

import geotrellis.vector.io.json.Implicits._
val geojson = features.toGeoJson()

最后只需要将 geojson 对象写入文件即可。

二、换种姿势

以上代码看上去轻描淡写,确实我折腾了好长时间才整出来。此处我又要插一句,有很多同学通过各种方式咨询我关于 GeoTrellis 使用的问题,我在这里要做一下声明:

首先,我知道的基本都毫无保留的写在博客里了,关于技术点问我和看我的博客差不多,我希望与大家一起探讨实现思路等方面的问题;

其次,就算问我,你是不是也得把问题描述清楚,有些同学截几句代码就要问我为什么跑不通,对不起,首先我不知道你从哪篇文章中截出的,其次我也不知道你要做什么事情,所以我真的无法回答;

第三,文章中的代码都是针对当时 GeoTrellis 版本而言的,你看到的时候可能已经更新了,可能会存在跑不通的情况,但是一般不存在,并且几时存在简单修改一下应该就可以,并且无需转牛角尖的问我类似 1TB Tiff 如何处理的问题,我的文章只提供技术方法,不做科学研究。

当然我非常感谢大家对我的认同和支持,这也是我持续写此博客集的动力之一(以后可能越来越少了,产品。。。都是产品惹的祸)。

言归正传,当我用 Scala 折腾了几天出来之后,一拍脑袋,不禁要骂自己几句,为什么我不直接拿 python 实现此功能呢?赶紧研究了一下,发现几行代码就搞定:

from geopandas import *

shpdata = GeoDataFrame.from_file(path)
features = [shpdata.__geo_interface__]

from json import dumps
geojson = open("demo.json", "w")
geojson.write(dumps({"type": "FeatureCollection", \
                     "features": features}, indent=2) + "\n")
geojson.close()

具体思路同上面分析,此处不具体展开,感兴趣的可以自行研究。

就像前面看到的一篇文章中讲到的,强大的不是 python 而是库,是的,一种语言用的人多了,自然库也就丰富了,而我们普通人可以选择为自己喜欢的语言贡献库,同时我们也不能死守一种语言,应该具体问题具体分析,找到最适合解决问题的语言,当然每一件事情都是学习的过程,都不会白费。

三、总结

本文介绍了两种语言下实现 Shp 转为 GeoJson 的方式,主要是分析解决问题的思路。

Geotrellis系列文章链接地址http://www.cnblogs.com/shoufengwei/p/5619419.html

版权声明:本文为shoufengwei原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/shoufengwei/p/9520374.html