package net.osmand.plus;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import net.osmand.Algoritms;
import net.osmand.IProgress;
import net.osmand.LogUtil;
import net.osmand.data.Amenity;
import net.osmand.data.AmenityType;
import net.osmand.data.index.IndexConstants;
import net.osmand.osm.Entity;
import net.osmand.osm.LatLon;
import net.osmand.osm.MapUtils;
import net.osmand.osm.Node;
import net.osmand.osm.io.IOsmStorageFilter;
import net.osmand.osm.io.OsmBaseStorage;
import net.sf.junidecode.Junidecode;
import org.apache.commons.logging.Log;
import org.xml.sax.SAXException;
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
public class AmenityIndexRepositoryOdb extends BaseLocationIndexRepository<Amenity> implements AmenityIndexRepository {
private static final Log log = LogUtil.getLog(AmenityIndexRepositoryOdb.class);
public final static int LIMIT_AMENITIES = 500;
// cache amenities
private String cFilterId;
private final String[] columns = new String[]{"id", "x", "y", "name", "name_en", "type", "subtype", "opening_hours", "phone", "site"}; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$//$NON-NLS-5$//$NON-NLS-6$//$NON-NLS-7$//$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$
public List<Amenity> searchAmenities(double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, int limit, PoiFilter filter, List<Amenity> amenities){
long now = System.currentTimeMillis();
String squery = "? < y AND y < ? AND ? < x AND x < ?"; //$NON-NLS-1$
if(filter != null){
String sql = filter.buildSqlWhereFilter();
if(sql != null){
squery += " AND " + sql; //$NON-NLS-1$
}
}
if(limit != -1){
squery += " ORDER BY RANDOM() LIMIT " +limit; //$NON-NLS-1$
}
Cursor query = db.query(IndexConstants.POI_TABLE, columns, squery,
new String[]{MapUtils.get31TileNumberY(topLatitude)+"", //$NON-NLS-1$
MapUtils.get31TileNumberY(bottomLatitude)+"", MapUtils.get31TileNumberX(leftLongitude)+"", //$NON-NLS-1$ //$NON-NLS-2$
MapUtils.get31TileNumberX(rightLongitude)+""}, null, null, null); //$NON-NLS-1$
if(query.moveToFirst()){
do {
Amenity am = new Amenity();
am.setId(query.getLong(0));
am.setLocation(MapUtils.get31LatitudeY(query.getInt(2)),
MapUtils.get31LongitudeX(query.getInt(1)));
am.setName(query.getString(3 ));
am.setEnName(query.getString(4));
if(am.getEnName().length() == 0){
am.setEnName(Junidecode.unidecode(am.getName()));
}
am.setType(AmenityType.fromString(query.getString(5)));
am.setSubType(query.getString(6));
am.setOpeningHours(query.getString(7));
am.setPhone(query.getString(8));
am.setSite(query.getString(9));
amenities.add(am);
if(limit != -1 && amenities.size() >= limit){
break;
}
} while(query.moveToNext());
}
query.close();
if (log.isDebugEnabled()) {
log.debug(String.format("Search for %s done in %s ms found %s.", //$NON-NLS-1$
topLatitude + " " + leftLongitude, System.currentTimeMillis() - now, amenities.size())); //$NON-NLS-1$
}
return amenities;
}
public boolean addAmenity(Amenity a){
insertAmenities(Collections.singleton(a));
return true;
}
public boolean updateAmenity(Amenity a){
StringBuilder b = new StringBuilder();
b.append("UPDATE " + IndexConstants.POI_TABLE + " SET "); //$NON-NLS-1$ //$NON-NLS-2$
b.append(" x = ?, "). //$NON-NLS-1$
append(" y = ?, "). //$NON-NLS-1$
append(" opening_hours = ?, "). //$NON-NLS-1$
append(" name = ?, "). //$NON-NLS-1$
append(" name_en = ?, ").//$NON-NLS-1$
append(" type = ?, "). //$NON-NLS-1$
append(" subtype = ? "). //$NON-NLS-1$
append(" site = ? "). //$NON-NLS-1$
append(" phone = ? "). //$NON-NLS-1$
append(" WHERE append( id = ?"); //$NON-NLS-1$
db.execSQL(b.toString(),
new Object[] { MapUtils.get31TileNumberX(a.getLocation().getLongitude()), MapUtils.get31TileNumberY(a.getLocation().getLatitude()),
a.getOpeningHours(), a.getName(), a.getEnName(), AmenityType.valueToString(a.getType()), a.getSubType(),
a.getSite(), a.getPhone(), a.getId()});
return true;
}
public boolean deleteAmenities(long id){
db.execSQL("DELETE FROM " + IndexConstants.POI_TABLE+ " WHERE id="+id); //$NON-NLS-1$ //$NON-NLS-2$
return true;
}
public synchronized void clearCache(){
super.clearCache();
cFilterId = null;
}
public void evaluateCachedAmenities(double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, int zoom, int limit, PoiFilter filter, List<Amenity> toFill){
cTopLatitude = topLatitude + (topLatitude -bottomLatitude);
cBottomLatitude = bottomLatitude - (topLatitude -bottomLatitude);
cLeftLongitude = leftLongitude - (rightLongitude - leftLongitude);
cRightLongitude = rightLongitude + (rightLongitude - leftLongitude);
cFilterId = filter == null? null :filter.getFilterId();
cZoom = zoom;
// first of all put all entities in temp list in order to not freeze other read threads
ArrayList<Amenity> tempList = new ArrayList<Amenity>();
searchAmenities(cTopLatitude, cLeftLongitude, cBottomLatitude, cRightLongitude, limit, filter, tempList);
synchronized (this) {
cachedObjects.clear();
cachedObjects.addAll(tempList);
}
checkCachedAmenities(topLatitude, leftLongitude, bottomLatitude, rightLongitude, cZoom, filter.getFilterId(), toFill);
}
public synchronized boolean checkCachedAmenities(double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, int zoom, String filterId, List<Amenity> toFill, boolean fillFound){
if (db == null) {
return true;
}
boolean inside = cTopLatitude >= topLatitude && cLeftLongitude <= leftLongitude && cRightLongitude >= rightLongitude
&& cBottomLatitude <= bottomLatitude && zoom == cZoom;
boolean noNeedToSearch = inside && Algoritms.objectEquals(filterId, cFilterId);
if((inside || fillFound) && toFill != null && Algoritms.objectEquals(filterId, cFilterId)){
for(Amenity a : cachedObjects){
LatLon location = a.getLocation();
if (location.getLatitude() <= topLatitude && location.getLongitude() >= leftLongitude && location.getLongitude() <= rightLongitude
&& location.getLatitude() >= bottomLatitude) {
toFill.add(a);
}
}
}
return noNeedToSearch;
}
public boolean checkCachedAmenities(double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, int zoom, String filterId, List<Amenity> toFill){
return checkCachedAmenities(topLatitude, leftLongitude, bottomLatitude, rightLongitude, zoom, filterId, toFill, false);
}
public boolean initialize(final IProgress progress, File file) {
return super.initialize(progress, file, IndexConstants.POI_TABLE_VERSION, IndexConstants.POI_TABLE, true);
}
public boolean updateAmenities(List<Amenity> amenities, double leftLon, double topLat, double rightLon, double bottomLat){
int l = MapUtils.get31TileNumberX(leftLon);
int r = MapUtils.get31TileNumberX(rightLon);
int t = MapUtils.get31TileNumberY(topLat);
int b = MapUtils.get31TileNumberY(bottomLat);
db.execSQL("DELETE FROM " + IndexConstants.POI_TABLE + " WHERE " + //$NON-NLS-1$ //$NON-NLS-2$
" x >= ? AND ? >= x AND " + //$NON-NLS-1$
" y >= ? AND ? >= y ", new Integer[] { l, r, t, b }); //$NON-NLS-1$
insertAmenities(amenities);
return true;
}
private void insertAmenities(Collection<Amenity> amenities) {
SQLiteStatement stat = db.compileStatement("DELETE FROM " + IndexConstants.POI_TABLE + " WHERE id = ?"); //$NON-NLS-1$//$NON-NLS-2$
for (Amenity a : amenities) {
stat.bindLong(1, a.getId());
stat.execute();
}
stat.close();
stat = db.compileStatement("INSERT INTO " + IndexConstants.POI_TABLE + //$NON-NLS-1$
"(id, x, y, name_en, name, type, subtype, opening_hours, site, phone) values(?,?,?,?,?,?,?,?,?,?)"); //$NON-NLS-1$
for (Amenity a : amenities) {
stat.bindLong(1, a.getId());
stat.bindDouble(2, MapUtils.get31TileNumberX(a.getLocation().getLongitude()));
stat.bindDouble(3, MapUtils.get31TileNumberY(a.getLocation().getLatitude()));
bindString(stat, 4, a.getEnName());
bindString(stat, 5, a.getName());
bindString(stat, 6, AmenityType.valueToString(a.getType()));
bindString(stat, 7, a.getSubType());
bindString(stat, 8 , a.getOpeningHours());
bindString(stat, 9, a.getSite());
bindString(stat, 10, a.getPhone());
stat.execute();
}
stat.close();
}
private final static String SITE_API = "http://api.openstreetmap.org/"; //$NON-NLS-1$
public static boolean loadingPOIs(List<Amenity> amenities, double leftLon, double topLat, double righLon, double bottomLat) {
try {
// bbox=left,bottom,right,top
String u = SITE_API+"api/0.6/map?bbox="+leftLon+","+bottomLat+","+righLon+","+topLat; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$
URL url = new URL(u);
log.info("Start loading poi : " + u); //$NON-NLS-1$
InputStream is = url.openStream();
OsmBaseStorage st = new OsmBaseStorage();
final Map<Amenity, Entity> amen = new LinkedHashMap<Amenity, Entity>();
final List<Amenity> tempList = new ArrayList<Amenity>();
st.getFilters().add(new IOsmStorageFilter(){
@Override
public boolean acceptEntityToLoad(OsmBaseStorage storage, Entity.EntityId id, Entity entity) {
Amenity.parseAmenities(entity, tempList);
if(!tempList.isEmpty()){
for(Amenity a : tempList){
amen.put(a, entity);
}
tempList.clear();
return true;
}
// to
return entity instanceof Node;
}
});
st.parseOSM(is, null, null, false);
for (Amenity am : amen.keySet()) {
// update location (when all nodes of way are loaded)
am.setEntity(amen.get(am));
if(am.getEnName().length() == 0){
am.setEnName(Junidecode.unidecode(am.getName()));
}
amenities.add(am);
}
log.info("Loaded " +amenities.size() + " amenities"); //$NON-NLS-1$//$NON-NLS-2$
} catch (IOException e) {
log.error("Loading nodes failed", e); //$NON-NLS-1$
return false;
} catch (SAXException e) {
log.error("Loading nodes failed", e); //$NON-NLS-1$
return false;
}
return true;
}
}