OsmDroid是一款安卓手机上的地图控件,包含了多种的基础图层:覆盖层、点图层(可包含多点)、线(只能包含单条Polyline)、面(只能包含单个Polygon)、底图图层。作为通用型的地图控件,其功能是非常强大的,但是面向于专业的Gis行业使用,难免有些许缺陷。本文主要讲解在OsmDroid上实现对于矢量数据的加载。
矢量数据的存在形式包含多种,常见的有服务发布形式(WMS、WFS)、本地文件形式(Shapefile)以及数据库存储形式。由于ArcGis在数据处理以及属性信息录入时其编码是电脑的默认编码(GBK),QGis可选择编码格式。但是数据处理人员不会在意这些在编码时才会产生的细节,所以一般都不会刻意的去生产UTF-8的编码格式数据。
为什么这里会说到编码的问题呢?OsmDroid现在包含的图层中只有WMS服务图层是可用于加载矢量数据的(还是测试中的哦),所以在完成加载的前提下还需要自己进行矢量数据的读取。
当矢量数据在安卓机上的存在形式为shapefile时,这个需要使用到gdal对其进行解析读取。
当矢量数据在安卓机或者在服务器上的存在形式为数据库文件时,这个时候需要使用基于sqlite的空间数据库插件进行读取。本次的数据存储环境为Spatialite.通过QGis创建Spatialite数据库,然后将矢量数据上传到数据库中。
下面是关于Spatialite数据库的操作类(截取了部分代码出来,文章最后又代码的共享连接):
public class SpatialiteHelper { /** * 数据库对象 */ private SQLiteDatabase sqLiteDatabase; public SpatialiteHelper(String path) { sqLiteDatabase = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READWRITE); }
//region 空间查询(涉及空间数据--需要将geom字段进行wkt转换--非空间查询建议不用此类查询方法,会降低效率) public boolean run(String sql){ try{ sqLiteDatabase.execSQL(sql); return true; }catch (Exception e){ return false; } } public boolean deleteGeom(String tableName, String wkt, String geometry) { try { String sql = String.format("Delete FROM %s WHERE ST_astext(%s)='%s'", tableName, geometry, wkt); sqLiteDatabase.execSQL(sql); return true; } catch (Exception e) { return false; } } /** * m模糊查询--关键字查询 * * @param tableName 表名 * @param keyWord 关键字 * @return the sq lite cursor models */ public SQLiteCursorModels fuzzyQuery(String tableName, String keyWord) { String cols = String.format("PRAGMA table_info([%s])", tableName); SQLiteCursorModels colsInfo = Query(cols); // String sql=String.format("select * from %s where ",tableName); String sql = String.format(" Select a.*,ASTEXT(b.geom) as geometry from %s a , %s b where a.id=b.id and(", tableName, tableName); for (SQLiteCursorModel model : colsInfo.getEntrys() ) { String colName = model.getColumns().get("name"); sql = String.format("%s a.%s like '%s%s%s' or", sql, colName, "%",keyWord,"%"); } sql = sql.substring(0, sql.length() - 2); sql = sql + ")"; return Query(sql); } /** * 对指定表名的表格进行空间查询 * * @param tableName 表名 * @return the array list */ public SQLiteCursorModels GeomQueryByTableName(String tableName) { String sql = String.format("Select a.*,ASTEXT(b.geom) as geometry from %s a , %s b where a.id=b.id", tableName, tableName); return GeomQuery(sql); } /** * 获取指定表格的指定字段(自动添加geom字段) * * @param tableName 表名 * @param fields 字段 * @return the array list */ public SQLiteCursorModels GeomQueryFiledsByTableName(String tableName, String[] fields) { String sql = "SELECT "; for (int i = 0; i < fields.length; i++) { sql = String.format("%s %s,", new Object[]{sql, fields[i]}); } sql = String.format("%s ASTEXT(%s) as geometry from %s ", new Object[]{sql, "geom", tableName}); return GeomQuery(sql); } /** * 空间查询 * * @param tableName 表名 * @param wkt the wkt * @param srid the srid * @param fields 字段集合 * @return the array list */ public SQLiteCursorModels QueryIntersectsByGemetry(String tableName, String wkt, int srid, String[] fields) { String sql = "SELECT "; for (int i = 0; i < fields.length; i++) { sql = String.format("%s %s,", new Object[]{sql, fields[i]}); } String createGeometry = String.format(" ST_GeomFromText('%s',%s)", wkt, srid); sql = String.format("%s ASTEXT(%s) as geometry from %s where ST_Intersects(%s,%s) = 1 ", new Object[]{sql, "geom", tableName, "geom", createGeometry}); return GeomQuery(sql); } /** * 获取指定表格的指定字段(自动添加geom字段) * * @param tableName 表名 * @param field 字段 * @param isClearEmpty 是否清除空值结果 * @return the array list */ public SQLiteCursorModels GeomQueryFiledsByTableName(String tableName, String field, boolean isClearEmpty) { String sql = String.format("SELECT %s , ASTEXT(%s) as geometry from %s", field, "geom", tableName); if (isClearEmpty) { sql = String.format("%s where %s<>''", sql, field, field); } return GeomQuery(sql); } /** * 单属性查询(适用于属性值类型为文本) * * @param tableName 表名 * @param field 字段名称 * @param value 属性 * @return the array list */ public SQLiteCursorModels GeomQueryBySingleAttribute(String tableName, String field, String value) { String sql = String.format("Select a.*,ASTEXT(b.geom) as geometry from %s a , %s b Where a.%s='%s' and a.id=b.id", tableName, tableName, field, value); return GeomQuery(sql); } /** * 单属性查询(适用于属性值类型为整形) * * @param tableName 表名 * @param field 字段名称 * @param value 属性 * @return the array list */ public SQLiteCursorModels GeomQueryBySingleAttribute(String tableName, String field, int value) { String sql = String.format("Select a.*,ASTEXT(b.geom) as geometry from %s a , %s b Where %s=%s", tableName, tableName, field, String.valueOf(value)); return GeomQuery(sql); } /** * 单属性查询(适用于属性值类型为双精度) * * @param tableName 表名 * @param field 字段名称 * @param value 属性 * @return the array list */ public SQLiteCursorModels GeomQueryBySingleAttribute(String tableName, String field, double value) { String sql = String.format("Select a.*,ASTEXT(b.geom) as geometry from %s a , %s b Where %s=%s", tableName, tableName, field, String.valueOf(value)); return GeomQuery(sql); } /** * 单属性查询(适用于属性值类型为单精度) * * @param tableName 表名 * @param field 字段名称 * @param value 属性 * @return the array list */ public SQLiteCursorModels GeomQueryBySingleAttribute(String tableName, String field, float value) { String sql = String.format("Select a.*,ASTEXT(b.geom) as geometry from %s a , %s b Where %s=%s", tableName, tableName, field, String.valueOf(value)); return GeomQuery(sql); } /** * 查询相交的要素Query intersects by gemetry array list. * * @param tableName 表格/图层名称 * @param wkt 目标图形的wkt * @param srid 坐标系代码 * @return the array list */ public SQLiteCursorModels QueryIntersectsByGemetry(String tableName, String wkt, int srid) { String createGeometry = String.format(" ST_GeomFromText('%s',%s)", wkt, srid); String sql = String.format("Select a.*,ASTEXT(b.geom) as geometry from %s a , %s b Where ST_Intersects(a.geom,%s) = 1 and ST_Intersects(b.geom,%s) = 1 ", tableName, tableName, createGeometry, createGeometry); return GeomQuery(sql); } public SQLiteCursorModels GeomQueryIntersectAndContain(String tableName, String wkt, int srid) { String createGeometry = String.format(" ST_GeomFromText('%s',%s)", wkt, srid); String sql = String.format("Select a.*,ASTEXT(b.geom) as geometry from %s a , %s b Where a.id=b.id and( ST_Intersects(a.geom,%s) = 1 or ST_Contains(%s,a.geom)=1)", tableName, tableName, createGeometry, createGeometry, createGeometry); return GeomQuery(sql); } /** * 空间查询 * * @param sql 查询语句 * @return the array list */ private SQLiteCursorModels GeomQuery(String sql) { Cursor cursor = sqLiteDatabase.rawQuery(sql, null); SQLiteCursorModels arrayList = new SQLiteCursorModels(); if (cursor != null && cursor.moveToFirst()) { arrayList = ConvertGeomCursorToList(cursor); } return arrayList; } /** * 将重置位置的游标传入,将会自动遍历游标到最后,并转换为序列 * * @param cursor * @return */ protected SQLiteCursorModels ConvertGeomCursorToList(Cursor cursor) { SQLiteCursorModels result = new SQLiteCursorModels(); while (!cursor.isAfterLast()) { SQLiteCursorModel tempModel = new SQLiteCursorModel(cursor); result.add(tempModel); cursor.moveToNext(); } return result; } //endregion /** * 关闭数据库连接 */ public void CloseConnection() { sqLiteDatabase.close(); } }
通过这个空间数据库的操作类,我们能获取到所有我们想获取到的数据,并且以SQLiteCursorModel这个类进行使用。下面就是对数据的加载(也就是绘制了)。当前以Polyline这种地理要素作为后面的讲解对象。
在文章的开头我们提到了OSMDroid对于线段的绘制是有一个继承于Overlay的Polyline类,但是这个类只能绘制出一条线,也就是一个覆盖图层只能绘制一个地理对象(Feature),而不是我们以为的FeatureLayer那种概念了。对于如何实现一个Layer加载多个地理要素目标,当前有两种解决方案:1.做一个管理类,在从数据库中获取到一个图层的所有要素数据后,将每个数据创建为OSM中的Polyline图层对象,然后我们自己创建一个类,它也继承与Overlay,同时有个关于Polyline的数组进行要素的存储,这样能够实现一个意义上类似于FeatureLayer的图层效果;2.做一个新的图层,它就是FeatureLayer.
此文讲述的是第一种方案,第二种方案将在下篇文章进行讲述。主要包含三个类来实现Gis上的要素概念:FeatureOverlaySet、FeatureOverlay、FeatureLayerOverlay.部分源码如下:
public class FeatureLayerOverlay { private FeatureOverlaySet featureOverlaySet; private String layerName; private String sourceName; private boolean isVisible = true; private List<Overlay> featureOverlay = new ArrayList<>(); public FeatureOverlaySet getFeatureOverlaySet() { return featureOverlaySet; } public String getLayerName() { return layerName; } public String getSourceName() { return sourceName; } public boolean isVisible() { return isVisible; } public void setVisible(boolean visible) { isVisible = visible; switch (featureOverlaySet.getGeometryType()) { case "MultiLineString": for (Overlay line : featureOverlay ) { PolylineOverlay polylineOverlay = (PolylineOverlay) line; polylineOverlay.setVisible(visible); } break; case "MultiPolygon": for (Overlay polygon : featureOverlay ) { PolygonOverlay polygonOverlay = (PolygonOverlay) polygon; polygonOverlay.setVisible(visible); } break; default: break; } } public List<Overlay> getFeatureOverlay() { return featureOverlay; } public FeatureLayerOverlay(FeatureOverlaySet featureOverlaySet, String layerName, String sourceName) { this.featureOverlaySet = featureOverlaySet; this.layerName = layerName; this.sourceName = sourceName; this.featureOverlaySet.setLayer(this); } }
传送门:https://github.com/Spe1993/SpeRemarks