上一篇章我们完成了规划项目图层与字段的存储结构,这一篇章我们就来根据这个自定义项目配置来生成shp,geojson和新版本的geodatabase

在不久前arcgis android迎来了100.14.0版本,在新版本中新增了本地创建geodatabase功能,这是一个很重要的功能,这使得我们可以不需要依赖shp来快速本地化保存要素,先进行预配置创建好一个项目,随意创建一个项目,在里面创建3个类型的图层,在图层里面随意创建一些字段,在字段的配置上需要注意,名称就是alias别名,字段就是存储在field,都不能为空。回到项目配置列表把它设置成为主项目。再返回即可

接下来回到项目配置列表点击项目右边的齿轮,即可创建shp,geodatabase,创建的文件在文件管理器主目录 ArcgisAndroid > 项目数据 > shp或者geodatabase目录下,其中geojson是运行时创建,是不在这里创建的

 创建完毕shp或者geodatabase的时候,回到主目录点击侧边栏,可以选择存储的类型,确定即可指定之后绘制要素是以什么方式来保存要素的,这个时候就可以快速进行绘制了

点击屏幕下方√即可完成保存,也可以把存储方式更改为geojson,这是一种轻量化文本存储方式,存放个几千几万个要素都不会有太大的性能问题,再多的话就推荐使用geodatabase来存储要素了,毕竟这可是个数据库。查询要素直接点击要素即可,会再次弹出绘制界面这次数据的改动算是一次更新要素。删除要素需要把屏幕正中心的 + 标识对准要素,点击删除确定即可删除

 可以看到出现了要素标注,这是因为我在配置图层的时候配置了要素的标注字段,这里读者可以修改代码成为选择的样式或许会更合适,标注字段内容就是下方面积字段的mj,这是个面图层,我还可以设置面的边框颜色和内容色,还可以设置透明度,还可以设置边框的宽度,如果是线图层的话,宽度就是本身的宽度,如果是面设置宽度即是面的边框宽度

 

如果将存储方式改为geojson的话,他会在本地自动生成一个xxxx.json的图层文件,geojson在生成的时候会把要素的符号化信息也一起存储在里面,这个可比shp强多了shp默认加载进来只是半透明的黑色样式

到了代码部分,创建shp我使用的是gdal.jar + so库组合来生成,因为本身runtime不支持创建shp文件,只能通过这个方式来实现

/**
 * 根据项目配置,生成shp文件
 */
fun createShp(project: Project) {
	//这里是  ArcgisAndroid/项目数据/shp
	val fileFolder = Environment.getExternalStorageDirectory().absolutePath + File.separator + getString(R.string.app_name) + File.separator + getString(R.string.project_data) + File.separator + getString(R.string.shp)
	val projectShpFolder = File(fileFolder, project.name)
	projectShpFolder.mkdirs()
	//在这个目录中创建项目目录 ArcgisAndroid/项目数据/shp/项目名字

	// 注册所有的驱动
	ogr.RegisterAll()
	// 为了支持中文路径,请添加下面这句代码
	gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8","YES")
	// 为了使属性表字段支持中文,请添加下面这句
	gdal.SetConfigOption("SHAPE_ENCODING","")
	//创建Driver
	val driver = ogr.GetDriverByName("ESRI Shapefile")

	//创建gdal的坐标系对象
	val spRefence = org.gdal.osr.SpatialReference(SpatialReference.create(4326).wkText)
	//根据ServiceFeatureTable类型创建gdal图层
	val vector = Vector<String>()
	vector.addElement("ENCODING=UTF-8")
	project.layers.forEachIndexed { index, layer ->
		//在这里创建具体的shp
		val shpPath = File(projectShpFolder, layer.name)
		shpPath.mkdirs()
		//得到dataSource
		val createDataSource = driver.CreateDataSource(shpPath.absolutePath, null)
		//创建图层
		val shapeLayer = when(layer.type){
			TYPE_POINT -> {
				createDataSource.CreateLayer(layer.name, spRefence, ogr.wkbPoint, vector)
			}
			TYPE_POLYLINE -> {
				createDataSource.CreateLayer(layer.name, spRefence, ogr.wkbLineString, vector)
			}
			TYPE_POLYGON -> {
				createDataSource.CreateLayer(layer.name, spRefence, ogr.wkbPolygon, vector)
			}
			else -> {
				createDataSource.CreateLayer(layer.name, spRefence, ogr.wkbUnknown, vector)
			}
		}
		//创建字段
		layer.fields.forEachIndexed { index, field ->
			val fieldDefn = FieldDefn(field.field, ogr.OFTString)
			shapeLayer.CreateField(fieldDefn)
		}
		//保存
		shapeLayer.SyncToDisk()
		shapeLayer.delete()
	}
}

 这是创建geodatabase关键代码

fun createGeodatabase(project: Project) {
	//这里是  ArcgisAndroid/项目数据/shp
	val fileFolder = Environment.getExternalStorageDirectory().absolutePath + File.separator + getString(R.string.app_name) + File.separator + getString(R.string.project_data) + File.separator + getString(R.string.geodatabase)
	val createAsync = Geodatabase.createAsync(fileFolder + File.separator + project.name + ".geodatabase")
	createAsync.addDoneListener {
		val geodatabase = createAsync.get()
		val asyncJobList = mutableListOf<Single<Any>>()
		project.layers.forEachIndexed { index, layer ->
			val geometryType = when (layer.type) {
				TYPE_POINT -> GeometryType.POINT
				TYPE_POLYLINE -> GeometryType.POLYLINE
				TYPE_POLYGON -> GeometryType.POLYGON
				else -> GeometryType.POINT
			}

			/**
			 * 数据表嘛,我统一口径用4326坐标系这一点你应该可以看到我用在了很多地方,无论是geojson还是shp还是geodatabase我都统一了,你也可以换成其他的
			 * 就连后面的featureServer我也会用4326坐标系
			 */
			val tableDescription = TableDescription(layer.name, SpatialReference.create(4326), geometryType)
			layer.fields.forEachIndexed { index, field ->
				//创建字段和类型,我之类全部都用TEXT,就是String的意思,不做其他格式的了你可以改成其他的也行
				val fieldDescription = FieldDescription(field.field, Field.Type.TEXT)
				fieldDescription.length = 64
				tableDescription.fieldDescriptions.add(fieldDescription)
			}
			val job = Single.create<Any> { emit->
				val createTableAsync = geodatabase.createTableAsync(tableDescription)
				createTableAsync.addDoneListener {
					try {
						val get = createTableAsync.get()
						emit.onSuccess(true)
					} catch (e: Exception) {
						e.printStackTrace()
						emit.onError(e)
					}
				}
			}
			asyncJobList.add(job)
		}
		//全部异步任务完成后再添加进来
		Single.zip(asyncJobList) { objects -> objects }.subscribe({
		   showToast("创建成功")
		}, {
		   showToast("创建失败")
		})
	}
}

创建geojson的组合是使用FeatureCollection + FeatureCollectionTable + FeatureCollectionLayer来完成,可以看到我在创建FeatureCollection的时候判断有没有本地的xxx.json文件,如果文件不存在,只是创建了一个空的geojson的对象存储模板,等保存要素到本地的时候就会去创建这个xxx.json文件,等下次进来程序就会直接读取这个xxx.json文件使用FeatureCollection.fromJson( jsonContent )来实例化出来,这个时候注意,注意,注意,千万不能再次创建FeatureCollectionTable,因为xxx.json中已经保存了上次的FeatureCollectionTable的内容了,如果再创建会错乱,例如上次创建了3个FeatureCollectionTable保存进json的时候也会把这个保存进去,下次创建FeatureCollection.fromJson的时候,FeatureCollection会自动创建FeatureCollectionTable和里面的字段关系的,要素的数据也会在这个所属的table里面,所以不能重新创建,通过这FeatureCollectionTable可以完成crud

/**
 * geoJson 数据操作
 */
private lateinit var featureCollection: FeatureCollection
private suspend fun loadProjectGeoJsonToMapView() = withContext(Dispatchers.IO) {
	val groupLayer = GroupLayer()
	//创建FeatureCollectionTable和字段关系
	val mainProjectName = SPUtils.getInstance().getString(App.app!!.getString(R.string.main_project))
	if(TextUtils.isEmpty(mainProjectName)){
		//占位,不然加载后面的featureServer会下标越界
		addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_PROJECT_FEATURE_INDEX)
		return@withContext
	}

	val project = projectRepository.getProjectByName(mainProjectName)
	//检查本地有没有主专业json文件
	val dataFolder = Environment.getExternalStorageDirectory().absolutePath + File.separator + getString(R.string.app_name) + File.separator + getString(R.string.project_data) + File.separator + getString(R.string.geojson)
	val jsonFile = File(dataFolder, "$mainProjectName.json")
	if(jsonFile.exists()){
		val jsonContent = FileUtil.readFile(jsonFile)
		featureCollection = FeatureCollection.fromJson(jsonContent)
	} else {
		featureCollection = FeatureCollection()
		//项目结构,只能初始化一次
		project.layers.forEachIndexed { index, layer ->
			val fieldList = layer.fields.map { Field.createString(it.field, it.alias, 32) }
			val geometryType = when(layer.type){
				TYPE_POINT -> GeometryType.POINT
				TYPE_POLYLINE -> GeometryType.POLYLINE
				TYPE_POLYGON -> GeometryType.POLYGON
				else -> GeometryType.POINT
			}
			// TODO: 一定要给项目图层字段中,设置 field 字段,空了不行
			if(fieldList.isNotEmpty()){
				//根据图层配置来创建FeatureCollectionTable
				val featureCollectionTable = FeatureCollectionTable(fieldList, geometryType, SpatialReference.create(4326))
				featureCollectionTable.title = layer.name
				featureCollection.tables.add(featureCollectionTable)
			}
		}
	}
	//设置符号化相关
	val featureCollectionLayer = FeatureCollectionLayer(featureCollection)
	featureCollectionLayer.addDoneLoadingListener {
		project.layers.forEachIndexed { index, layer ->
			val featureLayer = featureCollectionLayer.layers.filter { it.name == layer.name }[0]
			SymbolUtils.symbol(layer, featureLayer)
		}
		//把featureTable存起来,添加要素的时候用到
		featureCollection.tables.forEachIndexed { index, featureCollectionTable ->
			featureTable[featureCollectionTable.title] = featureCollectionTable
		}
		groupLayer.layers.add(featureCollectionLayer)
		addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_PROJECT_FEATURE_INDEX)
	}
	featureCollectionLayer.loadAsync()
}

不过使用geojson会有个弊端,就是每次保存和更新删除要素,都需要手动保存到本地来覆盖旧的xxx.json文件,他做不到自动保存,也或者是我暂时没能发现这样的api

这是保存到将FeatureCollection 保存到本地的代码

/**
 * 将featureCollection的内容保存到本地,是geojson
 */
private suspend fun featureCollectionSaveToJson(featureCollection: FeatureCollection) = withContext(Dispatchers.IO){
	//根据目录来遍历得到所有的文件
	val mainProjectName = SPUtils.getInstance().getString(getString(R.string.main_project))
	val dataFolder = Environment.getExternalStorageDirectory().absolutePath + File.separator + getString(R.string.app_name) + File.separator + getString(R.string.project_data) + File.separator + getString(R.string.geojson)
	FileUtil.write(File(dataFolder, "$mainProjectName.json"), featureCollection.toJson())
}

自此整个程序主体算是做好了,或许还有一些bug尚未发现~,从定义专业模板与图层还有字段的存储关系,还有初步的符号化与标注,还有创建shp,geojson,geodatabase来存储要素。后续篇章将为大家带来featureServer在线与离线模式的一体化操作

在最后还有一些小代码,增加了切换底图的操作,在侧边栏环境设置可以切换为mapbox,arcgis和天地图,天地图的key需要各位自己去创建,mapbox的key用的是我的开发key。默认我将ArcgisAndroid的底图设置成了mapbox,并对他做了浏览过保存到本地的缓存操作,不然使用程序99%的流量都是在请求一些看过的底图,这样浏览过的地方下次再缩放到该区域就会从本地缓存中直接读取而不是发请求去拿新的底图tile瓦片来消耗流量

https://gitee.com/tanqidi/ArcgisAndroidhttps://gitee.com/tanqidi/ArcgisAndroid

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐