/*
* Copyright 2012 Hannes Janetzek
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.database.postgis;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Properties;
import org.oscim.core.BoundingBox;
import org.oscim.core.GeoPoint;
import org.oscim.core.Tag;
import org.oscim.core.WebMercator;
import org.oscim.database.IMapDatabase;
import org.oscim.database.IMapDatabaseCallback;
import org.oscim.database.MapInfo;
import org.oscim.database.MapOptions;
import org.oscim.database.OpenResult;
import org.oscim.database.QueryResult;
import org.oscim.generator.JobTile;
import org.postgresql.PGConnection;
import android.util.Log;
/**
*
*
*/
public class MapDatabase implements IMapDatabase {
private final static String TAG = "MapDatabase";
private static final String QUERY = "SELECT tags, geom FROM __get_tile(?,?,?)";
private final float mScale = 1;
private int mCoordPos = 0;
private int mIndexPos = 0;
private float[] mCoords;
private short[] mIndex;
private Tag[] mTags;
private final MapInfo mMapInfo =
new MapInfo(new BoundingBox(-180, -85, 180, 85),
new Byte((byte) 14), new GeoPoint(53.11, 8.85),
WebMercator.NAME,
0, 0, 0, "de", "comment", "author", null);
private boolean mOpenFile = false;
private Connection connection = null;
private static volatile HashMap<Entry<String, String>, Tag> tagHash =
new HashMap<Entry<String, String>, Tag>(100);
private PreparedStatement prepQuery = null;
private boolean connect() {
Connection conn = null;
String dburl = "jdbc:postgresql://city.informatik.uni-bremen.de:5432/gis-2.0";
Properties dbOpts = new Properties();
dbOpts.setProperty("user", "osm");
dbOpts.setProperty("password", "osm");
dbOpts.setProperty("socketTimeout", "50");
dbOpts.setProperty("tcpKeepAlive", "true");
try {
DriverManager.setLoginTimeout(20);
System.out.println("Creating JDBC connection...");
Class.forName("org.postgresql.Driver");
conn = DriverManager.getConnection(dburl, dbOpts);
connection = conn;
prepQuery = conn.prepareStatement(QUERY);
PGConnection pgconn = (PGConnection) conn;
pgconn.addDataType("hstore", PGHStore.class);
conn.createStatement().execute("set statement_timeout to 60000");
} catch (Exception e) {
System.err.println("Aborted due to error:");
e.printStackTrace();
return false;
}
return true;
}
@Override
public QueryResult executeQuery(JobTile tile, IMapDatabaseCallback mapDatabaseCallback) {
if (connection == null) {
if (!connect())
return QueryResult.FAILED;
}
ResultSet r;
try {
prepQuery.setLong(1, tile.tileX * 256);
prepQuery.setLong(2, tile.tileY * 256);
prepQuery.setInt(3, tile.zoomLevel);
Log.d(TAG, "" + prepQuery.toString());
prepQuery.execute();
r = prepQuery.getResultSet();
} catch (SQLException e) {
e.printStackTrace();
connection = null;
return QueryResult.FAILED;
}
byte[] b = null;
PGHStore h = null;
// int cnt = 0;
try {
while (r != null && r.next()) {
mIndexPos = 0;
mCoordPos = 0;
// cnt++;
try {
Object obj = r.getObject(1);
h = null;
if (obj instanceof PGHStore)
h = (PGHStore) obj;
else {
Log.d(TAG, "no tags: skip way");
continue;
}
b = r.getBytes(2);
} catch (SQLException e) {
e.printStackTrace();
continue;
}
if (b == null) {
// Log.d(TAG, "no geometry: skip way");
continue;
}
mTags = new Tag[h.size()];
int i = 0;
for (Entry<String, String> t : h.entrySet()) {
if (t.getKey() == null) {
Log.d(TAG, "no KEY !!! ");
break;
}
Tag tag = tagHash.get(t);
if (tag == null) {
tag = new Tag(t.getKey(), t.getValue());
tagHash.put(t, tag);
}
mTags[i++] = tag;
}
if (i < mTags.length)
continue;
boolean polygon = parse(b);
if (mIndexPos == 0) {
Log.d(TAG, "no index: skip way");
continue;
} else if (mIndexPos == 1) {
mapDatabaseCallback.renderPointOfInterest((byte) 0, mTags,
mCoords[1], mCoords[0]);
} else {
short[] idx = new short[mIndexPos];
System.arraycopy(mIndex, 0, idx, 0, mIndexPos);
mapDatabaseCallback.renderWay((byte) 0, mTags, mCoords, idx, polygon, 0);
}
}
} catch (SQLException e) {
e.printStackTrace();
connection = null;
return QueryResult.FAILED;
}
// Log.d(TAG, "rows: " + cnt);
return QueryResult.SUCCESS;
}
@Override
public String getMapProjection() {
return WebMercator.NAME;
}
@Override
public MapInfo getMapInfo() {
return mMapInfo;
}
@Override
public boolean isOpen() {
return mOpenFile;
}
@Override
public OpenResult open(MapOptions options) {
mOpenFile = true;
if (mCoords == null) {
mCoords = new float[100000];
mIndex = new short[100000];
}
return OpenResult.SUCCESS;
}
@Override
public void close() {
if (connection != null) {
try {
connection.close();
} catch (SQLException e1) {
e1.printStackTrace();
} finally {
connection = null;
}
}
mCoords = null;
mIndex = null;
mOpenFile = false;
}
// taken from postgis-java
private static ValueGetter valueGetterForEndian(byte[] bytes) {
if (bytes[0] == ValueGetter.XDR.NUMBER) { // XDR
return new ValueGetter.XDR(bytes);
} else if (bytes[0] == ValueGetter.NDR.NUMBER) {
return new ValueGetter.NDR(bytes);
} else {
throw new IllegalArgumentException("Unknown Endian type:" + bytes[0]);
}
}
/**
* Parse a binary encoded geometry. Is synchronized to protect offset
* counter. (Unfortunately, Java does not have
* neither call by reference nor multiple return values.)
*
* @param value
* ...
* @return ...
*/
private boolean parse(byte[] value) {
return parseGeometry(valueGetterForEndian(value));
}
private boolean parseGeometry(ValueGetter data) {
byte endian = data.getByte(); // skip and test endian flag
if (endian != data.endian) {
throw new IllegalArgumentException("Endian inconsistency!");
}
int typeword = data.getInt();
int realtype = typeword & 0x1FFFFFFF; // cut off high flag bits
boolean haveZ = (typeword & 0x80000000) != 0;
boolean haveM = (typeword & 0x40000000) != 0;
boolean haveS = (typeword & 0x20000000) != 0;
// int srid = Geometry.UNKNOWN_SRID;
boolean polygon = false;
if (haveS) {
// srid = Geometry.parseSRID(data.getInt());
data.getInt();
}
switch (realtype) {
case Geometry.POINT:
parsePoint(data, haveZ, haveM);
break;
case Geometry.LINESTRING:
parseLineString(data, haveZ, haveM);
break;
case Geometry.POLYGON:
parsePolygon(data, haveZ, haveM);
polygon = true;
break;
case Geometry.MULTIPOINT:
parseMultiPoint(data);
break;
case Geometry.MULTILINESTRING:
parseMultiLineString(data);
break;
case Geometry.MULTIPOLYGON:
parseMultiPolygon(data);
polygon = true;
break;
case Geometry.GEOMETRYCOLLECTION:
parseCollection(data);
break;
default:
throw new IllegalArgumentException("Unknown Geometry Type: " + realtype);
}
// if (srid != Geometry.UNKNOWN_SRID) {
// result.setSrid(srid);
// }
return polygon;
}
private void parsePoint(ValueGetter data, boolean haveZ, boolean haveM) {
// double X = data.getDouble();
// double Y = data.getDouble();
mCoords[0] = (float) (data.getDouble() * mScale);
mCoords[1] = (float) (data.getDouble() * mScale);
mIndex[0] = 2;
mIndexPos = 1;
if (haveZ)
data.getDouble();
if (haveM)
data.getDouble();
}
/**
* Parse an Array of "full" Geometries
*
* @param data
* ...
* @param count
* ...
*/
private void parseGeometryArray(ValueGetter data, int count) {
for (int i = 0; i < count; i++) {
parseGeometry(data);
mIndex[mIndexPos++] = 0;
}
}
//
// private void parsePointArray(ValueGetter data, boolean haveZ, boolean haveM) {
// int count = data.getInt();
// for (int i = 0; i < count; i++) {
// parsePoint(data, haveZ, haveM);
// }
// }
private void parseMultiPoint(ValueGetter data) {
parseGeometryArray(data, data.getInt());
}
private void parseLineString(ValueGetter data, boolean haveZ, boolean haveM) {
int count = data.getInt();
for (int i = 0; i < count; i++) {
mCoords[mCoordPos++] = (float) (data.getDouble()) * mScale;
mCoords[mCoordPos++] = (float) (data.getDouble()) * mScale;
if (haveZ)
data.getDouble();
if (haveM)
data.getDouble();
}
mIndex[mIndexPos++] = (short) (count * 2);
}
private void parsePolygon(ValueGetter data, boolean haveZ, boolean haveM) {
int count = data.getInt();
for (int i = 0; i < count; i++) {
parseLineString(data, haveZ, haveM);
}
}
private void parseMultiLineString(ValueGetter data) {
int count = data.getInt();
parseGeometryArray(data, count);
}
private void parseMultiPolygon(ValueGetter data) {
int count = data.getInt();
parseGeometryArray(data, count);
}
private void parseCollection(ValueGetter data) {
int count = data.getInt();
parseGeometryArray(data, count);
}
@Override
public void cancel() {
// TODO Auto-generated method stub
}
}