From 7ff15e3a0c6dc2d26a517963a4df9becd904f21d Mon Sep 17 00:00:00 2001 From: Victor Shcherb Date: Wed, 5 May 2010 09:31:54 +0000 Subject: [PATCH] great refactoring git-svn-id: https://osmand.googlecode.com/svn/trunk@32 e29c36b1-1cfa-d876-8d93-3434fc2bb7b8 --- .../src/com/osmand/Algoritms.java | 3 +- DataExtractionOSM/src/com/osmand/LogUtil.java | 20 + .../src/com/osmand/MapPanel.java | 3 +- .../src/com/osmand/MapTileDownloader.java | 3 +- .../src/com/osmand/OsmandSettings.java | 2 + .../src/com/osmand/ToDoConstants.java | 18 + .../src/com/osmand/osm/io/OsmBaseStorage.java | 4 + OsmAnd/.classpath | 3 +- OsmAnd/AndroidManifest.xml | 12 +- OsmAnd/lib/bzip2-20090327.jar | Bin 0 -> 52093 bytes OsmAnd/res/layout/main.xml | 6 +- OsmAnd/res/values/strings.xml | 2 + OsmAnd/res/xml/settings_pref.xml | 1 + OsmAnd/src/com/osmand/LogUtil.java | 152 +++++++ .../com/osmand/activities/MapActivity.java | 108 ++++- .../osmand/activities/SettingsActivity.java | 46 +- .../com/osmand/views/OsmandMapTileView.java | 414 ++++++++++++------ OsmAnd/src/com/osmand/views/POIMapLayer.java | 154 +++++++ .../apache/commons/logging/LogFactory.java | 105 ----- 19 files changed, 793 insertions(+), 263 deletions(-) create mode 100644 DataExtractionOSM/src/com/osmand/LogUtil.java create mode 100644 OsmAnd/lib/bzip2-20090327.jar create mode 100644 OsmAnd/src/com/osmand/LogUtil.java create mode 100644 OsmAnd/src/com/osmand/views/POIMapLayer.java delete mode 100644 OsmAnd/src/org/apache/commons/logging/LogFactory.java diff --git a/DataExtractionOSM/src/com/osmand/Algoritms.java b/DataExtractionOSM/src/com/osmand/Algoritms.java index bab85a3f1a..7f939bb0be 100644 --- a/DataExtractionOSM/src/com/osmand/Algoritms.java +++ b/DataExtractionOSM/src/com/osmand/Algoritms.java @@ -7,14 +7,13 @@ import java.io.InputStream; import java.io.OutputStream; import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; /** * Basic algorithms that are not in jdk */ public class Algoritms { private static final int BUFFER_SIZE = 1024; - private static final Log log = LogFactory.getLog(Algoritms.class); + private static final Log log = LogUtil.getLog(Algoritms.class); public static boolean isEmpty(String s){ return s == null || s.length() == 0; diff --git a/DataExtractionOSM/src/com/osmand/LogUtil.java b/DataExtractionOSM/src/com/osmand/LogUtil.java new file mode 100644 index 0000000000..4c02b4f1fa --- /dev/null +++ b/DataExtractionOSM/src/com/osmand/LogUtil.java @@ -0,0 +1,20 @@ +package com.osmand; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * That class is replacing of standard LogFactory due to + * problems with Android implementation of LogFactory. + * See Android analog of LogUtil + * + * That class should be very simple & always use LogFactory methods, + * there is an intention to delegate all static methods to LogFactory. + */ +public class LogUtil { + + public static Log getLog(Class cl){ + return LogFactory.getLog(cl); + } + +} diff --git a/DataExtractionOSM/src/com/osmand/MapPanel.java b/DataExtractionOSM/src/com/osmand/MapPanel.java index 9d1960326e..2f30c6b099 100644 --- a/DataExtractionOSM/src/com/osmand/MapPanel.java +++ b/DataExtractionOSM/src/com/osmand/MapPanel.java @@ -32,7 +32,6 @@ import javax.swing.JPanel; import javax.swing.UIManager; import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import com.osmand.DataExtraction.ExitListener; import com.osmand.MapTileDownloader.DownloadRequest; @@ -48,7 +47,7 @@ public class MapPanel extends JPanel implements IMapDownloaderCallback { private static final long serialVersionUID = 1L; - protected static final Log log = LogFactory.getLog(MapPanel.class); + protected static final Log log = LogUtil.getLog(MapPanel.class); public static Menu getMenuToChooseSource(final MapPanel panel){ Menu tiles = new Menu("Source tile"); diff --git a/DataExtractionOSM/src/com/osmand/MapTileDownloader.java b/DataExtractionOSM/src/com/osmand/MapTileDownloader.java index fe729dfcad..fbd0c2d9d7 100644 --- a/DataExtractionOSM/src/com/osmand/MapTileDownloader.java +++ b/DataExtractionOSM/src/com/osmand/MapTileDownloader.java @@ -15,12 +15,11 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; public class MapTileDownloader { private static MapTileDownloader downloader = null; - private static Log log = LogFactory.getLog(MapTileDownloader.class); + private static Log log = LogUtil.getLog(MapTileDownloader.class); private ThreadPoolExecutor threadPoolExecutor; diff --git a/DataExtractionOSM/src/com/osmand/OsmandSettings.java b/DataExtractionOSM/src/com/osmand/OsmandSettings.java index a980e40894..2fd26fc74c 100644 --- a/DataExtractionOSM/src/com/osmand/OsmandSettings.java +++ b/DataExtractionOSM/src/com/osmand/OsmandSettings.java @@ -9,5 +9,7 @@ public class OsmandSettings { public static boolean showGPSLocationOnMap = DefaultLauncherConstants.showGPSCoordinates; public static ITileSource tileSource = DefaultLauncherConstants.MAP_defaultTileSource; + + public static boolean showPoiOverMap = true; } diff --git a/DataExtractionOSM/src/com/osmand/ToDoConstants.java b/DataExtractionOSM/src/com/osmand/ToDoConstants.java index e3d3d112fe..a90a3c5c46 100644 --- a/DataExtractionOSM/src/com/osmand/ToDoConstants.java +++ b/DataExtractionOSM/src/com/osmand/ToDoConstants.java @@ -1,5 +1,6 @@ package com.osmand; + /** * This class is designed to put all to do's and link them with code. * The whole methods could be paste or just constants. @@ -9,6 +10,9 @@ package com.osmand; public class ToDoConstants { + // use unknown implementation (not written)? How to see debug msgs? + // Explanation of how it works + // The task public int CONFIG_COMMONS_LOGGING_IN_ANDROID = 1; public int SAVE_SETTINGS_IN_ANDROID_BETWEEN_SESSION = 2; @@ -18,5 +22,19 @@ public class ToDoConstants { // OsmandMapTileView.java have problem with class loading (LogFactory, MapTileDownloader) - // it is not editable in editor public int MAKE_MAP_PANEL_EDITABLE_IN_EDITOR = 4; + + // common parts : work with cache on file system & in memory + public int EXTRACT_COMMON_PARTS_FROM_MAPPANEL_AND_OSMMAPVIEW = 5; + + + public int REVISE_MAP_ACTIVITY_HOLD_ALL_ZOOM_LATLON_IN_ONEPLACE = 6; + + /** + * Resource should cache all resources & free them + * if there is no enough memory @see tile cache in tile view + * @see poi index in map activity + */ + public int INTRODUCE_RESOURCE_MANAGER = 7; + } diff --git a/DataExtractionOSM/src/com/osmand/osm/io/OsmBaseStorage.java b/DataExtractionOSM/src/com/osmand/osm/io/OsmBaseStorage.java index 2ad5a3cf18..d84c5646b6 100644 --- a/DataExtractionOSM/src/com/osmand/osm/io/OsmBaseStorage.java +++ b/DataExtractionOSM/src/com/osmand/osm/io/OsmBaseStorage.java @@ -70,6 +70,7 @@ public class OsmBaseStorage extends DefaultHandler { } SAXParserFactory factory = SAXParserFactory.newInstance(); try { + factory.setFeature("http://xml.org/sax/features/namespace-prefixes", false); return saxParser = factory.newSAXParser(); } catch (ParserConfigurationException e) { throw new IllegalStateException(e); @@ -108,6 +109,8 @@ public class OsmBaseStorage extends DefaultHandler { @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { + name = saxParser.isNamespaceAware() ? localName : name; + if(!parseStarted){ if(!ELEM_OSM.equals(name) || !supportedVersions.contains(attributes.getValue(ATTR_VERSION))){ throw new OsmVersionNotSupported(); @@ -150,6 +153,7 @@ public class OsmBaseStorage extends DefaultHandler { @Override public void endElement(String uri, String localName, String name) throws SAXException { + name = saxParser.isNamespaceAware() ? localName : name; if (ELEM_NODE.equals(name) || ELEM_WAY.equals(name) || ELEM_RELATION.equals(name)) { if(currentParsedEntity != null){ if(acceptEntityToLoad(currentParsedEntity)){ diff --git a/OsmAnd/.classpath b/OsmAnd/.classpath index 7bded3bf9f..75991137f3 100644 --- a/OsmAnd/.classpath +++ b/OsmAnd/.classpath @@ -1,8 +1,9 @@ - + + diff --git a/OsmAnd/AndroidManifest.xml b/OsmAnd/AndroidManifest.xml index 463f52dfda..00f1161be1 100644 --- a/OsmAnd/AndroidManifest.xml +++ b/OsmAnd/AndroidManifest.xml @@ -10,20 +10,22 @@ - + + - + - - - + + + + \ No newline at end of file diff --git a/OsmAnd/lib/bzip2-20090327.jar b/OsmAnd/lib/bzip2-20090327.jar new file mode 100644 index 0000000000000000000000000000000000000000..7721279538c90ad9b1005a62352f56f89969d7e4 GIT binary patch literal 52093 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 literal 0 HcmV?d00001 diff --git a/OsmAnd/res/layout/main.xml b/OsmAnd/res/layout/main.xml index f676e7ed9b..dc3932d3f9 100644 --- a/OsmAnd/res/layout/main.xml +++ b/OsmAnd/res/layout/main.xml @@ -3,10 +3,12 @@ android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> - - + + + + diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 1e64dd59d9..a5a09e18fb 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -1,5 +1,7 @@ + Show POI on map + Show POI Choose the source of tiles Map tile source Map source diff --git a/OsmAnd/res/xml/settings_pref.xml b/OsmAnd/res/xml/settings_pref.xml index b88947b341..d354abae08 100644 --- a/OsmAnd/res/xml/settings_pref.xml +++ b/OsmAnd/res/xml/settings_pref.xml @@ -4,6 +4,7 @@ + diff --git a/OsmAnd/src/com/osmand/LogUtil.java b/OsmAnd/src/com/osmand/LogUtil.java new file mode 100644 index 0000000000..5385f26cc6 --- /dev/null +++ b/OsmAnd/src/com/osmand/LogUtil.java @@ -0,0 +1,152 @@ +package com.osmand; + +import org.apache.commons.logging.Log; + +/** + * That class is replacing of standard LogFactory due to + * problems with Android implementation of LogFactory. + * + * 1. It is impossible to replace standard LogFactory (that is hidden in android.jar) + * 2. Implementation of LogFactory always creates Logger.getLogger(String name) + * 3. + It is possible to enable logger level by calling + * Logger.getLogger("com.osmand").setLevel(Level.ALL); + * 4. Logger goes to low level android.util.Log where android.util.Log#isLoggable(String, int) is checked + * String tag -> is string of length 23 (stripped full class name) + * 5. It is impossible to set for all tags debug level (info is default) - android.util.Log#isLoggable(String, int). + * + */ +public class LogUtil { + public static String TAG = "com.osmand"; + private static class OsmandLogImplementation implements Log { + + private final String fullName; + private final String name; + + public OsmandLogImplementation(String name){ + this.fullName = name; + this.name = fullName.substring(fullName.lastIndexOf('.') + 1); + } + @Override + public void debug(Object message) { + if(isDebugEnabled()){ + android.util.Log.d(TAG, name + " " + message); + } + } + + @Override + public void debug(Object message, Throwable t) { + if(isDebugEnabled()){ + android.util.Log.d(TAG, name + " " + message, t); + } + } + + @Override + public void error(Object message) { + if(isErrorEnabled()){ + android.util.Log.e(TAG, name + " " + message); + } + } + + @Override + public void error(Object message, Throwable t) { + if(isErrorEnabled()){ + android.util.Log.e(TAG, name + " " + message, t); + } + } + + @Override + public void fatal(Object message) { + if(isFatalEnabled()){ + android.util.Log.e(TAG, name + " " + message); + } + + } + + @Override + public void fatal(Object message, Throwable t) { + if(isFatalEnabled()){ + android.util.Log.e(TAG, name + " " + message, t); + } + } + + @Override + public void info(Object message) { + if(isInfoEnabled()){ + android.util.Log.i(TAG, name + " " + message); + } + } + + @Override + public void info(Object message, Throwable t) { + if(isInfoEnabled()){ + android.util.Log.i(TAG, name + " " + message, t); + } + } + + @Override + public boolean isDebugEnabled() { + return android.util.Log.isLoggable(TAG, android.util.Log.DEBUG); + } + + @Override + public boolean isErrorEnabled() { + return android.util.Log.isLoggable(TAG, android.util.Log.ERROR); + } + + @Override + public boolean isFatalEnabled() { + return android.util.Log.isLoggable(TAG, android.util.Log.ERROR); + } + + @Override + public boolean isInfoEnabled() { + return android.util.Log.isLoggable(TAG, android.util.Log.INFO); + } + + @Override + public boolean isTraceEnabled() { + return android.util.Log.isLoggable(TAG, android.util.Log.DEBUG); + } + + @Override + public boolean isWarnEnabled() { + return android.util.Log.isLoggable(TAG, android.util.Log.WARN); + } + + @Override + public void trace(Object message) { + if(isTraceEnabled()){ + android.util.Log.d(TAG, name + " " + message); + } + } + + @Override + public void trace(Object message, Throwable t) { + if(isTraceEnabled()){ + android.util.Log.d(TAG, name + " " + message, t); + } + } + + @Override + public void warn(Object message) { + if(isWarnEnabled()){ + android.util.Log.w(TAG, name + " " + message); + } + } + + @Override + public void warn(Object message, Throwable t) { + if(isWarnEnabled()){ + android.util.Log.w(TAG, name + " " + message, t); + } + } + } + + public static Log getLog(String name){ + return new OsmandLogImplementation(name); + } + + public static Log getLog(Class cl){ + return getLog(cl.getName()); + } +} diff --git a/OsmAnd/src/com/osmand/activities/MapActivity.java b/OsmAnd/src/com/osmand/activities/MapActivity.java index 6e68fe7609..05a9b520e3 100644 --- a/OsmAnd/src/com/osmand/activities/MapActivity.java +++ b/OsmAnd/src/com/osmand/activities/MapActivity.java @@ -1,6 +1,13 @@ package com.osmand.activities; +import java.io.BufferedInputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.tools.bzip2.CBZip2InputStream; +import org.xml.sax.SAXException; import android.app.Activity; import android.content.Intent; @@ -16,11 +23,20 @@ import android.widget.Button; import android.widget.ImageButton; import android.widget.ZoomControls; +import com.osmand.Algoritms; import com.osmand.IMapLocationListener; +import com.osmand.LogUtil; import com.osmand.OsmandSettings; import com.osmand.R; +import com.osmand.data.DataTileManager; +import com.osmand.osm.Entity; +import com.osmand.osm.LatLon; import com.osmand.osm.MapUtils; +import com.osmand.osm.Node; +import com.osmand.osm.OSMSettings.OSMTagKey; +import com.osmand.osm.io.OsmBaseStorage; import com.osmand.views.OsmandMapTileView; +import com.osmand.views.POIMapLayer; import com.osmand.views.PointOfView; public class MapActivity extends Activity implements LocationListener, IMapLocationListener { @@ -35,11 +51,18 @@ public class MapActivity extends Activity implements LocationListener, IMapLocat private PointOfView pointOfView; + private static final String TILES_PATH = "osmand/tiles/"; + private static final String POI_PATH = "osmand/poi/"; + private static final org.apache.commons.logging.Log log = LogUtil.getLog(MapActivity.class); + + private DataTileManager indexPOI; + + private POIMapLayer poiMapLayer; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - + requestWindowFeature(Window.FEATURE_NO_TITLE); // getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, // WindowManager.LayoutParams.FLAG_FULLSCREEN); @@ -47,7 +70,7 @@ public class MapActivity extends Activity implements LocationListener, IMapLocat setContentView(R.layout.main); mapView = (OsmandMapTileView) findViewById(R.id.MapView); - mapView.setFileWithTiles(new File(Environment.getExternalStorageDirectory(), "osmand/tiles/")); + mapView.setFileWithTiles(new File(Environment.getExternalStorageDirectory(), TILES_PATH)); mapView.addMapLocationListener(this); ZoomControls zoomControls = (ZoomControls) findViewById(R.id.ZoomControls01); @@ -55,12 +78,14 @@ public class MapActivity extends Activity implements LocationListener, IMapLocat @Override public void onClick(View v) { mapView.setZoom(mapView.getZoom() + 1); + poiMapLayer.setCurrentLocationAndZoom(poiMapLayer.getCurrentLocation(), mapView.getZoom()); } }); zoomControls.setOnZoomOutClickListener(new OnClickListener() { @Override public void onClick(View v) { mapView.setZoom(mapView.getZoom() - 1); + poiMapLayer.setCurrentLocationAndZoom(poiMapLayer.getCurrentLocation(), mapView.getZoom()); } }); @@ -95,7 +120,63 @@ public class MapActivity extends Activity implements LocationListener, IMapLocat LocationManager service = (LocationManager) getSystemService(LOCATION_SERVICE); service.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, this); + indexPOI = indexPOI(); + + poiMapLayer = (POIMapLayer)findViewById(R.id.PoiMapLayer); + poiMapLayer.setNodeManager(indexPOI); + } + + private static final boolean indexPOIFlag = false; + + public DataTileManager indexPOI(){ + File file = new File(Environment.getExternalStorageDirectory(), POI_PATH); + + DataTileManager r = new DataTileManager(); + if(file.exists() && file.canRead() && indexPOIFlag){ + for(File f : file.listFiles() ){ + if(f.getName().endsWith(".bz2") || f.getName().endsWith(".osm") ){ + if(log.isDebugEnabled()){ + log.debug("Starting index POI " + f.getAbsolutePath()); + } + boolean zipped = f.getName().endsWith(".bz2"); + InputStream stream = null; + try { + OsmBaseStorage storage = new OsmBaseStorage(); + stream = new FileInputStream(f); + stream = new BufferedInputStream(stream); + if (zipped) { + if (stream.read() != 'B' || stream.read() != 'Z') { + log.error("Can't read poi file " + f.getAbsolutePath() + + "The source stream must start with the characters BZ if it is to be read as a BZip2 stream."); + continue; + } else { + stream = new CBZip2InputStream(stream); + } + } + storage.parseOSM(stream); + for(Entity e : storage.getRegisteredEntities().values()){ + if(e instanceof Node && e.getTag(OSMTagKey.AMENITY) != null){ + Node n = (Node) e; + r.registerObject(n.getLatitude(), n.getLongitude(), n); + } + } + if(log.isDebugEnabled()){ + log.debug("Finishing index POI " + f.getAbsolutePath()); + } + } catch(IOException e){ + log.error("Can't read poi file " + f.getAbsolutePath(), e); + } catch (SAXException e) { + log.error("Can't read poi file " + f.getAbsolutePath(), e); + } finally { + Algoritms.closeStream(stream); + } + } + } + } + return r; + } + @Override @@ -148,6 +229,12 @@ public class MapActivity extends Activity implements LocationListener, IMapLocat } } + @Override + protected void onSaveInstanceState(Bundle outState) { + // TODO Auto-generated method stub + super.onSaveInstanceState(outState); + } + @Override protected void onPause() { @@ -162,16 +249,31 @@ public class MapActivity extends Activity implements LocationListener, IMapLocat if(mapView.getMap() != OsmandSettings.tileSource){ mapView.setMap(OsmandSettings.tileSource); } + if((poiMapLayer.getVisibility() == View.VISIBLE) != OsmandSettings.showPoiOverMap){ + if(OsmandSettings.showPoiOverMap){ + poiMapLayer.setVisibility(View.VISIBLE); + } else { + poiMapLayer.setVisibility(View.INVISIBLE); + } + } + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + mapView.onLowMemory(); } @Override public void locationChanged(double newLatitude, double newLongitude, Object source) { - // when user + // when user start dragging if(source == mapView && lastKnownLocation != null){ linkLocationWithMap = false; backToLocation.setVisibility(View.VISIBLE); } + poiMapLayer.setCurrentLocationAndZoom(new LatLon(newLatitude, newLongitude), mapView.getZoom()); + validatePointOfView(); } diff --git a/OsmAnd/src/com/osmand/activities/SettingsActivity.java b/OsmAnd/src/com/osmand/activities/SettingsActivity.java index 3f05861a76..62e4341efb 100644 --- a/OsmAnd/src/com/osmand/activities/SettingsActivity.java +++ b/OsmAnd/src/com/osmand/activities/SettingsActivity.java @@ -2,12 +2,6 @@ package com.osmand.activities; import java.util.List; -import com.osmand.DefaultLauncherConstants; -import com.osmand.R; -import com.osmand.R.xml; -import com.osmand.map.TileSourceManager; -import com.osmand.map.TileSourceManager.TileSourceTemplate; - import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.ListPreference; @@ -16,11 +10,19 @@ import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; import android.preference.Preference.OnPreferenceChangeListener; +import com.osmand.OsmandSettings; +import com.osmand.R; +import com.osmand.map.TileSourceManager; +import com.osmand.map.TileSourceManager.TileSourceTemplate; + public class SettingsActivity extends PreferenceActivity implements OnPreferenceChangeListener { private static final String use_internet_to_download_tiles = "use_internet_to_download_tiles"; private static final String show_gps_location_text = "show_gps_location_text"; private static final String map_tile_sources = "map_tile_sources"; + private static final String show_poi_over_map = "show_poi_over_map"; + private CheckBoxPreference showGpsLocation; + private CheckBoxPreference showPoiOnMap; private CheckBoxPreference useInternetToDownloadTiles; private ListPreference tileSourcePreference; @@ -33,6 +35,8 @@ public class SettingsActivity extends PreferenceActivity implements OnPreference showGpsLocation.setOnPreferenceChangeListener(this); useInternetToDownloadTiles =(CheckBoxPreference) screen.findPreference(use_internet_to_download_tiles); useInternetToDownloadTiles.setOnPreferenceChangeListener(this); + showPoiOnMap =(CheckBoxPreference) screen.findPreference(show_poi_over_map); + showPoiOnMap.setOnPreferenceChangeListener(this); tileSourcePreference =(ListPreference) screen.findPreference(map_tile_sources); tileSourcePreference.setOnPreferenceChangeListener(this); @@ -43,8 +47,10 @@ public class SettingsActivity extends PreferenceActivity implements OnPreference @Override protected void onResume() { super.onResume(); - useInternetToDownloadTiles.setChecked(DefaultLauncherConstants.loadMissingImages); - showGpsLocation.setChecked(DefaultLauncherConstants.showGPSCoordinates); + useInternetToDownloadTiles.setChecked(OsmandSettings.useInternetToDownloadTiles); + showGpsLocation.setChecked(OsmandSettings.showGPSLocationOnMap); + showPoiOnMap.setChecked(OsmandSettings.showPoiOverMap); + List list = TileSourceManager.getKnownSourceTemplates(); String[] entries = new String[list.size()]; for(int i=0; i cacheOfImages = new HashMap(); - // cached data to draw images - private Bitmap[][] images; - // this value is always <= 0 - private int xStartingImage = 0; - // this value is always <= 0 - private int yStartingImage = 0; - - Map cacheOfImages = new WeakHashMap(); - private PointF startDragging = null; + private boolean isStartedDragging = false; + private double startDraggingX = 0d; + private double startDraggingY = 0d; + private PointF initStartDragging = null; Paint paintGrayFill; Paint paintWhiteFill; Paint paintBlack; - final Handler mHandler = new Handler(); + private AnimatedDragging animatedDraggingThread; - // Create runnable for posting - final Runnable invalidateView = new Runnable() { - public void run() { - invalidate(); - } - }; public OsmandMapTileView(Context context, AttributeSet attrs) { @@ -112,10 +113,11 @@ public class OsmandMapTileView extends View implements IMapDownloaderCallback { paintBlack = new Paint(); paintBlack.setStyle(Style.STROKE); paintBlack.setColor(Color.BLACK); - - prepareImage(); + setClickable(true); + getHolder().addCallback(this); downloader.setDownloaderCallback(this); + asyncLoadingTiles.start(); } @@ -124,31 +126,103 @@ public class OsmandMapTileView extends View implements IMapDownloaderCallback { } - public void dragTo(PointF p){ - double dx = (startDragging.x - (double)p.x)/getTileSize(); - double dy = (startDragging.y - (double)p.y)/getTileSize(); + public void dragTo(double fromX, double fromY, double toX, double toY){ + double dx = (fromX - toX)/getTileSize(); + double dy = (fromY - toY)/getTileSize(); this.latitude = MapUtils.getLatitudeFromTile(zoom, getYTile() + dy); this.longitude = MapUtils.getLongitudeFromTile(zoom, getXTile() + dx); prepareImage(); - fireMapLocationListeners(this); + // TODO +// fireMapLocationListeners(this); } + public class AnimatedDragging extends Thread { + private float curX; + private float curY; + private float vx; + private float vy; + private float ax; + private float ay; + private byte dirX; + private byte dirY; + private long time = System.currentTimeMillis(); + private boolean stopped; + private final float a = 0.0005f; + + public AnimatedDragging(float dTime, float startX, float startY, float endX, float endY) { + vx = Math.abs((endX - startX)/dTime); + vy = Math.abs((endY - startY)/dTime); + dirX = (byte) (endX > startX ? 1 : -1); + dirY = (byte) (endY > startY ? 1 : -1); + ax = vx * a; + ay = vy * a; + } + + @Override + public void run() { + try { + while ((vx > 0 || vy > 0) && !isStartedDragging && !stopped) { + sleep((long) (40d/(Math.max(vx, vy)+0.45))); + long curT = System.currentTimeMillis(); + int dt = (int) (curT - time); + float newX = vx > 0 ? curX + dirX * vx * dt : curX; + float newY = vy > 0 ? curY + dirY * vy * dt : curY; + if(!isStartedDragging){ + dragTo(curX, curY, newX, newY); + } + vx -= ax * dt; + vy -= ay * dt; + time = curT; + curX = newX; + curY = newY; + } + } catch (InterruptedException e) { + } + animatedDraggingThread = null; + } + + public void stopEvaluation(){ + stopped = true; + } + + } + + + @Override public boolean onTouchEvent(MotionEvent event) { if(event.getAction() == MotionEvent.ACTION_DOWN) { - if(startDragging == null){ - startDragging = new PointF(event.getX(), event.getY()); + if(animatedDraggingThread != null){ + animatedDraggingThread.stopEvaluation(); + } + if(!isStartedDragging){ + startDraggingX = event.getX(); + startDraggingY = event.getY(); + isStartedDragging = true; + initStartDragging = new PointF(event.getX(), event.getY()); } } else if(event.getAction() == MotionEvent.ACTION_UP) { - if(startDragging != null){ - dragTo(new PointF(event.getX(), event.getY())); - startDragging = null; + if(isStartedDragging){ + dragTo(startDraggingX, startDraggingY, event.getX(), event.getY()); + if(event.getEventTime() - event.getDownTime() < timeForDraggingAnimation && + Math.abs(event.getX() - initStartDragging.x) + Math.abs(event.getY() - initStartDragging.y) > minimumDistanceForDraggingAnimation){ + float timeDist = (int) (event.getEventTime() - event.getDownTime()); + if(timeDist < 20){ + timeDist = 20; + } + animatedDraggingThread = new AnimatedDragging(timeDist, initStartDragging.x, initStartDragging.y, + event.getX(), event.getY()); + isStartedDragging = false; + animatedDraggingThread.start(); + } + isStartedDragging = false; + } } else if(event.getAction() == MotionEvent.ACTION_MOVE) { - if(startDragging != null){ - PointF p = new PointF(event.getX(), event.getY()); - dragTo(p); - startDragging = p; + if(isStartedDragging){ + dragTo(startDraggingX, startDraggingY, event.getX(), event.getY()); + startDraggingX = event.getX(); + startDraggingY = event.getY(); } } return super.onTouchEvent(event); @@ -178,31 +252,6 @@ public class OsmandMapTileView extends View implements IMapDownloaderCallback { } } - @Override - protected void onDraw(Canvas canvas) { - canvas.drawRect(0,0, getWidth(), getHeight(), paintWhiteFill); - if (images != null) { - for (int i = 0; i < images.length; i++) { - for (int j = 0; j < images[i].length; j++) { - if (images[i][j] == null) { - drawEmptyTile(canvas, i*getTileSize()+xStartingImage, j * getTileSize() + yStartingImage); - } else { - canvas.drawBitmap(images[i][j], i * getTileSize() + xStartingImage, j * getTileSize() + yStartingImage, null); - } - } - } - } - canvas.drawCircle(getWidth()/2, getHeight()/2, 3, paintBlack); - canvas.drawCircle(getWidth()/2, getHeight()/2, 6, paintBlack); - if (OsmandSettings.showGPSLocationOnMap) { - canvas.drawText(MessageFormat.format("Lat : {0}, lon : {1}, zoom : {2}", latitude, longitude, zoom), - drawCoordinatesX, drawCoordinatesY, paintBlack); - } - } - - - - @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { prepareImage(); @@ -214,31 +263,68 @@ public class OsmandMapTileView extends View implements IMapDownloaderCallback { return map.getName() +"/"+zoom+"/"+(x) +"/"+y+ext+".tile"; } - public Bitmap getImageFor(int x, int y, int zoom, boolean loadIfNeeded) { - if (map == null || fileWithTiles == null || !fileWithTiles.canRead()) { + public AsyncLoadingThread asyncLoadingTiles = new AsyncLoadingThread(); + + public class AsyncLoadingThread extends Thread { + Map requests = Collections.synchronizedMap(new LinkedHashMap()); + + public AsyncLoadingThread(){ + super("Async loading tiles"); + } + + @Override + public void run() { + while(true){ + try { + boolean update = false; + while(!requests.isEmpty()){ + String f = requests.keySet().iterator().next(); + DownloadRequest r = requests.remove(f); + // TODO last param + getImageForTile(r.xTile, r.yTile, r.zoom, OsmandSettings.useInternetToDownloadTiles); + update = true; + } + if(update){ + prepareImage(); + } + sleep(350); + } catch (InterruptedException e) { + log.error(e); + } catch (RuntimeException e){ + log.error(e); + } + } + } + + public void requestToLoadImage(String s, DownloadRequest req){ + requests.put(s, req); + + } + }; + + private Bitmap getImageForTile(int x, int y, int zoom, boolean loadIfNeeded){ + String file = getFileForImage(x, y, zoom, map.getTileFormat()); + if(fileWithTiles == null || !fileWithTiles.canRead()){ return null; } - - String file = getFileForImage(x, y, zoom, map.getTileFormat()); - if (cacheOfImages.get(file) == null) { - File en = new File(fileWithTiles, file); - if (cacheOfImages.size() > maxImgCacheSize) { - ArrayList list = new ArrayList(cacheOfImages.keySet()); - for (int i = 0; i < list.size(); i += 2) { - Bitmap bmp = cacheOfImages.remove(list.get(i)); - bmp.recycle(); + File en = new File(fileWithTiles, file); + if (cacheOfImages.size() > maxImgCacheSize) { + onLowMemory(); + } + + if (!downloader.isFileCurrentlyDownloaded(en)) { + if (en.exists()) { + long time = System.currentTimeMillis(); + cacheOfImages.put(file, BitmapFactory.decodeFile(en.getAbsolutePath())); + if (log.isDebugEnabled()) { + log.debug("Loaded file : " + file + " " + -(time - System.currentTimeMillis()) + " ms"); } - System.gc(); - } - if (!downloader.isFileCurrentlyDownloaded(en)) { - if (en.exists()) { - long time = System.currentTimeMillis(); - cacheOfImages.put(file, BitmapFactory.decodeFile(en.getAbsolutePath())); - if (log.isDebugEnabled()) { - log.debug("Loaded file : " + file + " " + -(time - System.currentTimeMillis()) + " ms"); - } - } - if(loadIfNeeded && cacheOfImages.get(file) == null){ + } + + if(loadIfNeeded && cacheOfImages.get(file) == null){ + ConnectivityManager mgr = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo info = mgr.getActiveNetworkInfo(); + if (info != null && info.isConnected()) { String urlToLoad = map.getUrlToLoad(x, y, zoom); if (urlToLoad != null) { downloader.requestToDownload(urlToLoad, new DownloadRequest(en, x, y, zoom)); @@ -246,55 +332,110 @@ public class OsmandMapTileView extends View implements IMapDownloaderCallback { } } } - return cacheOfImages.get(file); } - @Override + public Bitmap getImageFor(int x, int y, int zoom, boolean loadIfNeeded) { + if (map == null) { + return null; + } + String file = getFileForImage(x, y, zoom, map.getTileFormat()); + if (cacheOfImages.get(file) == null && loadIfNeeded) { + // TODO use loadIfNeeded + asyncLoadingTiles.requestToLoadImage(file, new DownloadRequest(null, x, y, zoom)); + } + return cacheOfImages.get(file); + } + + public void tileDownloaded(String dowloadedUrl, DownloadRequest request) { - int tileSize = getTileSize(); - double xTileLeft = getXTile() - getWidth() / (2d * tileSize); - double yTileUp = getYTile() - getHeight() / (2d * tileSize); - int i = request.xTile - (int)xTileLeft; - int j = request.yTile - (int)yTileUp; - if(request.zoom == this.zoom && - (i >= 0 && i < images.length) && (j >= 0 && j < images[i].length)) { - images[i][j] = getImageFor(request.xTile, request.yTile, zoom, false); - mHandler.post(invalidateView); - } - } - - public void prepareImage(){ - prepareImage(OsmandSettings.useInternetToDownloadTiles); - } - - public void prepareImage(boolean loadNecessaryImages) { - if (loadNecessaryImages) { - downloader.refuseAllPreviousRequests(); - } - double xTileLeft = getXTile() - getWidth() / (2d * getTileSize()); - double xTileRight = getXTile() + getWidth() / (2d * getTileSize()); - double yTileUp = getYTile() - getHeight() / (2d * getTileSize()); - double yTileDown = getYTile() + getHeight() / (2d * getTileSize()); - - xStartingImage = -(int) ((xTileLeft - Math.floor(xTileLeft)) * getTileSize()); - yStartingImage = -(int) ((yTileUp - Math.floor(yTileUp)) * getTileSize()); - - int tileXCount = ((int) xTileRight - (int) xTileLeft + 1); - int tileYCount = ((int) yTileDown - (int) yTileUp + 1); - images = new Bitmap[tileXCount][tileYCount]; - for (int i = 0; i < images.length; i++) { - for (int j = 0; j < images[i].length; j++) { - images[i][j] = getImageFor((int) xTileLeft + i, (int) yTileUp + j, zoom, loadNecessaryImages); + int xTileLeft = (int) Math.floor(getXTile() - getWidth() / (2d * getTileSize())); + int yTileUp = (int) Math.floor(getYTile() - getHeight() / (2d * getTileSize())); + int startingX = (int) ((xTileLeft - getXTile()) * getTileSize() + getWidth() / 2); + int startingY = (int) ((yTileUp - getYTile()) * getTileSize() + getHeight() / 2); + int i = (request.xTile - xTileLeft) * getTileSize() + startingX; + int j = (request.yTile - yTileUp) * getTileSize() + startingY; + if (request.zoom == this.zoom && + (i + getTileSize() >= 0 && i < getWidth()) && (j + getTileSize() >= 0 && j < getHeight())) { + SurfaceHolder holder = getHolder(); + synchronized (holder) { + Canvas canvas = holder.lockCanvas(new Rect(i, j, getTileSize() + i, getTileSize() + j)); + if (canvas != null) { + try { + Bitmap bmp = getImageFor(request.xTile, request.yTile, zoom, true); + if (bmp == null) { + drawEmptyTile(canvas, i, j); + } else { + canvas.drawBitmap(bmp, i, j, null); + } + drawOverMap(canvas); + } finally { + holder.unlockCanvasAndPost(canvas); + } + } + } + } + } + private MessageFormat formatOverMap = new MessageFormat("Lat : {0}, lon : {1}, zoom : {2}, mem : {3}"); + java.util.Formatter formatterOMap = new java.util.Formatter(); + private ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + private void drawOverMap(Canvas canvas){ + canvas.drawCircle(getWidth() / 2, getHeight() / 2, 3, paintBlack); + canvas.drawCircle(getWidth() / 2, getHeight() / 2, 6, paintBlack); + if (OsmandSettings.showGPSLocationOnMap) { + float f= (Runtime.getRuntime().totalMemory())/ 1e6f; + formatterOMap = new Formatter(); + canvas.drawText(formatterOMap.format("Lat : %.3f, lon : %.3f, zoom : %d, mem : %.3f", latitude, longitude, zoom, f).toString(), + drawCoordinatesX, drawCoordinatesY, paintBlack); +// canvas.drawText(formatOverMap.format(new Object[]{latitude, longitude, zoom, f}), drawCoordinatesX, +// drawCoordinatesY, paintBlack); + } + } + + public void prepareImage() { + if (OsmandSettings.useInternetToDownloadTiles) { + downloader.refuseAllPreviousRequests(); + } + int width = getWidth(); + int height = getHeight(); + int tileSize = getTileSize(); + + int xTileLeft = (int) Math.floor(getXTile() - width / (2d * getTileSize())); + int yTileUp = (int) Math.floor(getYTile() - height / (2d * getTileSize())); + int startingX = (int) ((xTileLeft - getXTile()) * getTileSize() + getWidth() / 2); + int startingY = (int) ((yTileUp - getYTile()) * getTileSize() + getHeight() / 2); + + SurfaceHolder holder = getHolder(); + synchronized (holder) { + Canvas canvas = holder.lockCanvas(); + if (canvas != null) { + try { + for (int i = 0; i * tileSize + startingX < width; i++) { + for (int j = 0; j * tileSize + startingY < height; j++) { + Bitmap bmp = getImageFor(xTileLeft + i, yTileUp + j, zoom, true); + if (bmp == null) { + drawEmptyTile(canvas, i * tileSize + startingX, j * tileSize + startingY); + } else { + canvas.drawBitmap(bmp, i * tileSize + startingX, j * tileSize + startingY, null); + } + } + } + drawOverMap(canvas); + } finally { + holder.unlockCanvasAndPost(canvas); + } } } - invalidate(); } public void setZoom(int zoom){ if (map == null || (map.getMaximumZoomSupported() >= zoom && map.getMinimumZoomSupported() <= zoom)) { + if(animatedDraggingThread != null){ + animatedDraggingThread.stopEvaluation(); + } this.zoom = zoom; prepareImage(); } @@ -344,6 +485,19 @@ public class OsmandMapTileView extends View implements IMapDownloaderCallback { } + public void onLowMemory(){ + log.info("On low memory : cleaning tiles - size = " + cacheOfImages.size()); + ArrayList list = new ArrayList(cacheOfImages.keySet()); + // remove first images (as we think they are older) + for (int i = 0; i < list.size()/2; i ++) { + Bitmap bmp = cacheOfImages.remove(list.get(i)); + if(bmp != null){ + bmp.recycle(); + } + } + System.gc(); + } + public void addMapLocationListener(IMapLocationListener l){ listeners.add(l); @@ -359,7 +513,21 @@ public class OsmandMapTileView extends View implements IMapDownloaderCallback { } } + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + prepareImage(); + } + @Override + public void surfaceCreated(SurfaceHolder holder) { + prepareImage(); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + // TODO clear cache ? + + } } diff --git a/OsmAnd/src/com/osmand/views/POIMapLayer.java b/OsmAnd/src/com/osmand/views/POIMapLayer.java new file mode 100644 index 0000000000..c64a2c72ea --- /dev/null +++ b/OsmAnd/src/com/osmand/views/POIMapLayer.java @@ -0,0 +1,154 @@ +package com.osmand.views; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Point; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Toast; + +import com.osmand.DefaultLauncherConstants; +import com.osmand.LogUtil; +import com.osmand.OsmandSettings; +import com.osmand.data.DataTileManager; +import com.osmand.map.ITileSource; +import com.osmand.osm.LatLon; +import com.osmand.osm.MapUtils; +import com.osmand.osm.Node; +import com.osmand.osm.OSMSettings.OSMTagKey; + +public class POIMapLayer extends View { + private DataTileManager nodeManager = null; + private LatLon currentLocation = null; + private int zoomLevel = DefaultLauncherConstants.MAP_startMapZoom; + private Map points = new LinkedHashMap(); + private Paint pointUI; + private static final int radiusClick = 16; + private Toast previousShownToast =null; + private final static Log log = LogUtil.getLog(POIMapLayer.class); + + + public POIMapLayer(Context context, AttributeSet attrs) { + super(context, attrs); + initUI(); + } + + public POIMapLayer(Context context) { + super(context); + initUI(); + } + + private void initUI() { + pointUI = new Paint(); + pointUI.setColor(Color.CYAN); + pointUI.setAlpha(150); + pointUI.setAntiAlias(true); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + for(Node n : points.keySet()){ + Point p = points.get(n); + canvas.drawCircle(p.x, p.y, radiusClick/2, pointUI); + } + } + + public void preparePoints() { + points.clear(); + if (nodeManager != null && currentLocation != null) { + double tileNumberX = MapUtils.getTileNumberX(zoomLevel, currentLocation.getLongitude()); + double tileNumberY = MapUtils.getTileNumberY(zoomLevel, currentLocation.getLatitude()); + double xTileLeft = tileNumberX - getWidth() / (2d * getTileSize()); + double xTileRight = tileNumberX + getWidth() / (2d * getTileSize()); + double yTileUp = tileNumberY - getHeight() / (2d * getTileSize()); + double yTileDown = tileNumberY + getHeight() / (2d * getTileSize()); + + List objects = nodeManager.getObjects(MapUtils.getLatitudeFromTile(zoomLevel, yTileUp), + MapUtils.getLongitudeFromTile(zoomLevel, xTileLeft), + MapUtils.getLatitudeFromTile(zoomLevel, yTileDown), + MapUtils.getLongitudeFromTile(zoomLevel, xTileRight)); + for (Node o : objects) { + double tileX = MapUtils.getTileNumberX(zoomLevel, o.getLongitude()); + int x = (int) ((tileX - xTileLeft) * getTileSize()); + double tileY = MapUtils.getTileNumberY(zoomLevel, o.getLatitude()); + int y = (int) ((tileY - yTileUp) * getTileSize()); + points.put(o, new Point(x, y)); + } + } + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if(event.getAction() == MotionEvent.ACTION_DOWN) { + + if(previousShownToast != null){ + previousShownToast.cancel(); + previousShownToast = null; + } + int x = (int) event.getX(); + int y = (int) event.getY(); + + for(Node n : points.keySet()){ + Point p = points.get(n); + if(Math.abs(p.x - x) <= radiusClick && Math.abs(p.y - y) <= radiusClick){ + StringBuilder b = new StringBuilder(); + b.append("This is an amenity : \n"); + b.append("type - ").append(n.getTag(OSMTagKey.AMENITY)).append("\n"); + if(n.getTag(OSMTagKey.NAME) != null){ + b.append("name - ").append(n.getTag(OSMTagKey.NAME)).append("\n"); + } + b.append("id - ").append(n.getId()); + + previousShownToast = Toast.makeText(getContext(), b.toString(), Toast.LENGTH_SHORT); + previousShownToast.show(); + // TODO use precision + log.debug("Precision is " + event.getXPrecision()); + return true; + } + } + } + return super.onTouchEvent(event); + } + + public void setCurrentLocationAndZoom(LatLon currentLocation, int zoom) { + this.currentLocation = currentLocation; + this.zoomLevel = zoom; + preparePoints(); + } + + public int getTileSize(){ + ITileSource source = OsmandSettings.tileSource; + return source == null ? 256 : source.getTileSize(); + + } + + public void setNodeManager(DataTileManager nodeManager) { + this.nodeManager = nodeManager; + preparePoints(); + } + + public LatLon getCurrentLocation() { + return currentLocation; + } + + public DataTileManager getNodeManager() { + return nodeManager; + } + + + public int getZoomLevel() { + return zoomLevel; + } + +} diff --git a/OsmAnd/src/org/apache/commons/logging/LogFactory.java b/OsmAnd/src/org/apache/commons/logging/LogFactory.java deleted file mode 100644 index 9582683557..0000000000 --- a/OsmAnd/src/org/apache/commons/logging/LogFactory.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.apache.commons.logging; - -public class LogFactory { - public static String TAG = "com.osmand"; - - public static Log getLog(Class cl){ - final String name = cl.getName(); - return new Log() { - @Override - public void debug(Object message) { - android.util.Log.d(TAG, name + " " + message); - - } - - @Override - public void debug(Object message, Throwable t) { - android.util.Log.d(TAG, name + " " + message, t); - } - - @Override - public void error(Object message) { - android.util.Log.e(TAG, name + " " + message); - } - - @Override - public void error(Object message, Throwable t) { - android.util.Log.e(TAG, name + " " + message, t); - } - - @Override - public void fatal(Object message) { - android.util.Log.e(TAG, name + " " + message); - } - - @Override - public void fatal(Object message, Throwable t) { - android.util.Log.e(TAG, name + " " + message, t); - } - - @Override - public void info(Object message) { - android.util.Log.i(TAG, name + " " + message); - } - - @Override - public void info(Object message, Throwable t) { - android.util.Log.i(TAG, name + " " + message, t); - - } - - @Override - public boolean isDebugEnabled() { - return android.util.Log.isLoggable(TAG, android.util.Log.DEBUG); - } - - @Override - public boolean isErrorEnabled() { - return android.util.Log.isLoggable(TAG, android.util.Log.ERROR); - } - - @Override - public boolean isFatalEnabled() { - return android.util.Log.isLoggable(TAG, android.util.Log.ERROR); - } - - @Override - public boolean isInfoEnabled() { - return android.util.Log.isLoggable(TAG, android.util.Log.INFO); - } - - @Override - public boolean isTraceEnabled() { - return android.util.Log.isLoggable(TAG, android.util.Log.DEBUG); - } - - @Override - public boolean isWarnEnabled() { - return android.util.Log.isLoggable(TAG, android.util.Log.WARN); - } - - @Override - public void trace(Object message) { - android.util.Log.d(TAG, name + " " + message); - } - - @Override - public void trace(Object message, Throwable t) { - android.util.Log.d(TAG, name + " " + message, t); - } - - @Override - public void warn(Object message) { - android.util.Log.w(TAG, name + " " + message); - - } - - @Override - public void warn(Object message, Throwable t) { - android.util.Log.w(TAG, name + " " + message, t); - } - - }; - } - -}