Open sourcing OSMfocus v0.1.1r1

This commit is contained in:
MichaelVL 2018-07-09 20:08:42 +02:00
parent 914c54f80d
commit 5b781c989a
44 changed files with 4391 additions and 1 deletions

View file

@ -1,2 +1,40 @@
# osm-focus
OSMFocus Android application
OSMFocus Android application - OpenStreetMap Data for Android
![Image](images/featuregfx.png?raw=true)
OSMfocus can show details of nearby objects from the OpenStreetMap database such
that they can be compared with real world observations. Correcting errors or
adding missing information to OpenStreetMap and thus getting it 'into focus' is
the main purpose of OSMfocus. OSMfocus is not a map, navigation tool or
OpenStreetMap editor.
Discrepancies between the real-world and OpenStreetMap are best observed on-site
while memorizing details for later comparison with OpenStreetMap is usually
difficult and with a good likelihood of missing the actual differences.
![Image](images/screen01.png?raw=true)
![Image](images/screen02.png?raw=true)
![Image](images/screen03.png?raw=true)
![Image](images/screen04.png?raw=true)
OSMfocus shows key-value pairs as = and abbreviate two pairs that occur quite
often, namely highway= and name= which are shown as :. Way objects which has
just a single key-value pair of type 'building=*' are ignored when searching for
nearby objects. To make better utilization of screen space, the following keys
are not shown: 'kms:*', 'osak:*', 'created_by', 'addr:country', 'addr:postcode'
and 'source'.
OSMfocus use location services and network access permissions for downloading
vector map data and background map tiles for your current location.
OSMfocus use data from OpenStreetMap (www.openstreetmap.org). This data and
screenshots containing maps are (C) Copyright OpenSteetMap contributors.
[OSMfocus on Google play](https://play.google.com/store/apps/details?id=dk.network42.osmfocus)
# About the Name OSMfocus
The thinking behind the application name is that the application will help
"sharpening" the OpenStreetMap data such that it represents a more clear view of
the observable world.

BIN
images/featuregfx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 KiB

BIN
images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

BIN
images/screen01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

BIN
images/screen02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

BIN
images/screen03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

BIN
images/screen04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 KiB

View file

@ -0,0 +1,11 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
</RelativeLayout>

13
res/menu/main.xml Normal file
View file

@ -0,0 +1,13 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="@string/action_settings"/>
<item android:id="@+id/action_download" android:title="@string/action_download"/>
<item android:id="@+id/action_testdownload" android:title="@string/action_testdownload"/>
<item android:id="@+id/action_loadcache" android:title="@string/action_loadcache"/>
<item android:id="@+id/action_whereami" android:title="@string/action_whereami"/>
<item android:id="@+id/action_togglehud" android:title="@string/action_togglehud"/>
</menu>

View file

@ -0,0 +1,15 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/action_whereami"
android:title="@string/action_whereami"
android:icon="@drawable/ic_action_location_found"
android:showAsAction="always" />
<item android:id="@+id/action_download"
android:title="@string/action_download"
android:icon="@drawable/ic_downloadosm"
android:showAsAction="never" />
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:showAsAction="never" />
</menu>

62
res/values/arrays.xml Normal file
View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="labelTextSize">
<item name="tiny">Tiny</item>
<item name="small">Small</item>
<item name="medium">Medium</item>
<item name="medium_plus">Medium+</item>
<item name="large">Large</item>
<item name="huge">Huge</item>
</string-array>
<string-array name="labelTextSizeValues">
<item name="tiny">8</item>
<item name="small">10</item>
<item name="medium">11</item>
<item name="medium_plus">12</item>
<item name="large">14</item>
<item name="huge">18</item>
</string-array>
<string-array name="labelPoiNum">
<item>Auto</item>
<item>2 (top of screen)</item>
<item>4</item>
<item>8</item>
</string-array>
<string-array name="labelPoiNumValues">
<item>0</item>
<item>2</item>
<item>4</item>
<item>8</item>
</string-array>
<string-array name="labelBackMapType">
<item>None</item>
<item>OSM Tiles</item>
<item>OSM Cyclemap Tiles</item>
<item>Internal Vector (experimental)</item>
</string-array>
<string-array name="labelBackMapTypeValues">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</string-array>
<string-array name="labelAutoload">
<item>Manual</item>
<item>Automatic/Small</item>
<item>Automatic/Large</item>
</string-array>
<string-array name="labelAutoloadValues">
<item>1</item>
<item>2</item>
<item>3</item>
</string-array>
</resources>

7
res/values/dimens.xml Normal file
View file

@ -0,0 +1,7 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

39
res/values/strings.xml Normal file
View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">OSMfocus</string>
<string name="action_settings">Settings</string>
<string name="action_download">Download</string>
<string name="action_testdownload">TestDownload</string>
<string name="action_whereami">Where am I?</string>
<string name="action_togglehud">Toggle debug HUD</string>
<string name="action_loadcache">Load From Cache</string>
<string name="info_nodataloaded">No map data loaded.</string>
<string name="info_pressandhold">Press and hold to open menu.</string>
<string name="info_enabled">Enabled</string>
<string name="info_disabled">Disabled</string>
<string name="info_locationunknown">Waiting for location</string>
<string name="info_locationoutside">Location outside downloaded area</string>
<string name="info_downloading">Downloading area</string>
<string name="info_errdownloadarea">Could not download area!</string>
<string name="info_notshowpoiatzoomlevel">POIs not shown at high zoom levels</string>
<string name="info_notshowvectoratzoomlevel">Vector data not shown at high zoom levels</string>
<string name="pref_autoload">Auto Download</string>
<string name="pref_autoload_summ">Enable automatic download of map data</string>
<string name="pref_wireframe">Wireframe Map</string>
<string name="pref_poilines">Show POI lines</string>
<string name="pref_poinum">Number of POIs on screen</string>
<string name="pref_poinum_summ">Number of POIs on screen</string>
<string name="pref_labelsize">POI Label Size</string>
<string name="pref_labelsize_summ">Size of font used for element labels</string>
<string name="pref_backmap">Background Map Type</string>
<string name="pref_backmap_summ">Type of background map to show</string>
<string name="info_osm_copyright">&#169; OpenStreetMap contributors</string>
</resources>

20
res/values/styles.xml Normal file
View file

@ -0,0 +1,20 @@
<resources>
<!--
Base application theme, dependent on API level. This theme is replaced
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
-->
<style name="AppBaseTheme" parent="android:Theme.Holo.Light">
<!--
Theme customizations available in newer API levels can go in
res/values-vXX/styles.xml, while customizations related to
backward-compatibility can go here.
-->
</style>
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
</style>
</resources>

39
res/xml/preferences.xml Normal file
View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<ListPreference
android:key="pref_autoload"
android:title="@string/pref_autoload"
android:summary="@string/pref_autoload_summ"
android:defaultValue="2"
android:entries="@array/labelAutoload"
android:entryValues="@array/labelAutoloadValues" />
<!-- <CheckBoxPreference
android:key="pref_wireframe"
android:title="@string/pref_wireframe"
android:defaultValue="false" /> -->
<ListPreference
android:key="pref_poinum"
android:title="@string/pref_poinum"
android:summary="@string/pref_poinum_summ"
android:defaultValue="0"
android:entries="@array/labelPoiNum"
android:entryValues="@array/labelPoiNumValues" />
<CheckBoxPreference
android:key="pref_poilines"
android:title="@string/pref_poilines"
android:defaultValue="true" />
<ListPreference
android:key="pref_labelsize"
android:title="@string/pref_labelsize"
android:summary="@string/pref_labelsize_summ"
android:defaultValue="11"
android:entries="@array/labelTextSize"
android:entryValues="@array/labelTextSizeValues" />
<ListPreference
android:key="pref_backmaptype"
android:title="@string/pref_backmap"
android:summary="@string/pref_backmap_summ"
android:defaultValue="2"
android:entries="@array/labelBackMapType"
android:entryValues="@array/labelBackMapTypeValues" />
</PreferenceScreen>

View file

@ -0,0 +1,63 @@
package dk.network42.osmfocus;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.sql.Timestamp;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
public class CustomExceptionHandler implements UncaughtExceptionHandler {
private static String TAG = "CustomExceptionHandler";
private UncaughtExceptionHandler mDefaultHandler;
private SharedData mG;
private String mUrl;
public CustomExceptionHandler(SharedData g, String url) {
mG = g;
mUrl = url;
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
public void uncaughtException(Thread t, Throwable e) {
Log.e(TAG, "Exception:"+e);
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
final Writer result = new StringWriter();
final PrintWriter printWriter = new PrintWriter(result);
e.printStackTrace(printWriter);
mG.printState(printWriter);
String data = result.toString();
printWriter.close();
String filename = timestamp + ".txt";
writeFile(filename, data);
mDefaultHandler.uncaughtException(t, e);
}
private void writeFile(String filename, String data) {
String state = Environment.getExternalStorageState();
if ( ! Environment.MEDIA_MOUNTED.equals(state)) {
Log.e(TAG, "External media not available");
}
String filenm = mG.mCtx.getExternalFilesDir(null)+ "/" + filename;
Log.e(TAG, "Writing crash data to file '"+filenm+"' = "+data);
try {
FileOutputStream os = new FileOutputStream(filenm);
os.write(data.getBytes());
os.close();
} catch (IOException e) {
Log.e(TAG, "Error writing " + filenm, e);
}
Log.e(TAG, "Done writing crash data to file");
}
}

View file

@ -0,0 +1,15 @@
package dk.network42.osmfocus;
public class Filter {
public void filter(OsmElement e) {
// if (e.getTagCnt() == 21) { // FIXME
// e.mFiltered = true;
// }
if (e.getTagCnt() == 1 && e.hasTag("building", "yes")) { // FIXME
e.mFiltered = true;
}
// if (e.hasTag("landuse", "residential")) { // FIXME
// e.mFiltered = true;
// }
}
}

View file

@ -0,0 +1,45 @@
package dk.network42.osmfocus;
import android.util.Log;
// WGS84 based bounding box
public class GeoBBox {
private static final String TAG = "GeoBBox";
// primary attributes, multiplied by 1E7 (OSM style), all floats are derived from these
public int left, right; // left <= right
public int bottom, top; // bottom <= top
public boolean empty = true;
public void check() {
assert(left <= right);
assert(bottom <= top);
}
public GeoBBox(final int left, final int bottom, final int right, final int top) {
this.left = left;
this.bottom = bottom;
this.right = right;
this.top = top;
this.empty = false;
check();
}
public GeoBBox(final double left, final double bottom, final double right, final double top) {
this((int)(left*1E7), (int)(bottom*1E7), (int)(right*1E7), (int)(top*1E7));
}
public String toString() {
return "["+left/1E7+","+bottom/1E7+"--"+right/1E7+","+top/1E7+"]";
}
// Get BBox centered at point with given size. Size ('meters') measured to both sides of central
// point, i.e. box will be 2*meters wide
public static GeoBBox getBoxForPoint(final double lat, final double lon, final double meters) {
double dax = GeoMath.convertMetersToGeoDistancePar(meters, lat);
double day = GeoMath.convertMetersToGeoDistanceMed(meters);
GeoBBox box = new GeoBBox(lon-dax, lat-day, lon+dax, lat+day);
//Log.d(TAG, "GeoBBox for point ("+lat+","+lon+") size="+meters+" -> deltaAngles="+dax+","+day+", box="+box);
return box;
}
}

View file

@ -0,0 +1,120 @@
package dk.network42.osmfocus;
public class GeoMath {
// Convenience constants
private static final double _180_PI = 180.0/Math.PI;
private static final double _PI_2 = Math.PI/2.0;
private static final double _PI_4 = Math.PI/4.0;
private static final double _PI_180 = Math.PI/180.0;
private static final double _PI_360 = Math.PI/360.0;
// Maximum latitude due to Mercator projection.
public static final double MAX_LAT = _180_PI * Math.atan(Math.sinh(Math.PI));
// See http://en.wikipedia.org/wiki/Earth_ellipsoid
public static final double EARTH_POLAR_RADIUS = 6356752;
public static final double EARTH_EQUATORIAL_RADIUS = 6378137;
// Reference Earth radius in meters (mean between equatorial and polar WGS84 radii).
public static final double EARTH_RADIUS = (EARTH_EQUATORIAL_RADIUS+EARTH_POLAR_RADIUS)/2;
// Approximate conversions between 'on the ground' meter measure to angle [degrees]
// One equator
public static double convertMetersToGeoDistanceEq(double meters) {
return _180_PI*meters/EARTH_EQUATORIAL_RADIUS;
}
// On parallel at a given latitude
public static double convertMetersToGeoDistancePar(double meters, double lat) {
return _180_PI*meters/EARTH_EQUATORIAL_RADIUS/Math.cos(lat*_PI_180);
}
// On median
public static double convertMetersToGeoDistanceMed(double meters) {
return _180_PI*meters/EARTH_POLAR_RADIUS;
}
// Mercator projection. Truncated to MAX_LAT if lat is outside +-MAX_LAT.
// http://en.wikipedia.org/wiki/Mercator_projection#Mathematics_of_the_projection
public static double latToMercator(double lat) {
lat = Math.min(MAX_LAT, lat);
lat = Math.max(-MAX_LAT, lat);
return _180_PI * Math.log(Math.tan(lat*_PI_360 + _PI_4));
}
// Inverse mercator projection
public static double mercatorToLat(double merc) {
return _180_PI * (2.0 * Math.atan(Math.exp(merc*_PI_180)) - _PI_2);
}
public static double getMercatorScale(double lat) {
return latToMercator(lat)/lat;
}
// Closest point on finite line (l1 to l2) from point (p)
// Algorithm, see http://paulbourke.net/geometry/pointlineplane/
public static double[] nearestPoint(double px, double py, double l1x, double l1y, double l2x, double l2y) {
double x, y;
double dx = l2x - l1x;
double dy = l2y - l1y;
if (dx == 0.0 && dy == 0.0) {
x = l1x;
y = l1y;
} else {
double u;
u = ((px-l1x)*dx + (py-l1y)*dy) / (dx*dx+dy*dy);
if (u <= 0.0) {
x = l1x;
y = l1y;
} else if (u >= 1.0) {
x = l2x;
y = l2y;
} else {
x = l1x + u*dx;
y = l1y + u*dy;
}
}
return new double []{x, y};
}
// Distance from point (p) to finite line (l1 to l2)
public static double getLineDistance(double px, double py, double l1x, double l1y, double l2x, double l2y) {
double x, y;
double dx = l2x - l1x;
double dy = l2y - l1y;
if (dx == 0.0 && dy == 0.0) {
x = l1x;
y = l1y;
} else {
double u;
u = ((px-l1x)*dx + (py-l1y)*dy) / (dx*dx+dy*dy);
if (u <= 0.0) {
x = l1x;
y = l1y;
} else if (u >= 1.0) {
x = l2x;
y = l2y;
} else {
x = l1x + u*dx;
y = l1y + u*dy;
}
}
return Math.hypot(px-x, py-y);
}
// Shorten line with 'by' from (l1x,l1y)
public static double[] shortenLine(double l1x, double l1y, double l2x, double l2y, double by) {
double dx = l2x - l1x;
double dy = l2y - l1y;
if ((dx == 0.0 && dy == 0.0) || (by <= 0)) {
return new double []{l1x, l1y};
}
double len = Math.hypot(dx, dy);
if (len <= by) {
return new double []{l2x, l2y};
}
double x, y, u;
u = by/len;
x = l1x + u*dx;
y = l1y + u*dy;
return new double []{x, y};
}
}

View file

@ -0,0 +1,705 @@
package dk.network42.osmfocus;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.res.Configuration;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.NotificationCompat;
import android.support.v4.view.GestureDetectorCompat;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.WindowManager;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.LocationClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
public class MainActivity extends Activity implements
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener,
LocationListener, GpsStatus.Listener,
SensorEventListener {
static final int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;
static final int PREFERENCE_REQUEST = 9001;
static final int INVALIDATE_VIEW = 1000;
static final int POLL_NOTIFICATIONS = 1001;
private static final String TAG = "OsmFocusActivity";
public static final String PREFS_NAME = "OSMFocusPrefsFile";
LocationManager mLocationManager;
LocationClient mLocationClient;
LocationRequest mLocationRequest;
SensorManager sensorManager;
private Sensor sensorAccelerometer;
private Sensor sensorMagneticField;
private float[] valuesAccelerometer;
private float[] valuesMagneticField;
private float[] matrixR;
private float[] matrixI;
private float[] matrixValues;
private MapView mapView;
double mPanLon, mPanLat;
private OsmServer mOsmServer = new OsmServer(null, "Editor");
private GestureDetectorCompat mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
private boolean mScaleInProgress = false;
SharedData mG = null;
public double mLonLastUpd=0, mLatLastUpd=0; // Where mapview was last updated
NotificationManager mNotificationManager;
Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedData.checkAppUpdate(getApplicationContext());
PreferenceManager.setDefaultValues(this, R.xml.preferences, true);
//setContentView(R.layout.activity_main);
//getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
// WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
//requestWindowFeature(Window.FEATURE_NO_TITLE);
getActionBar().setDisplayShowTitleEnabled(false);
Log.i(TAG, "API level "+Build.VERSION.SDK_INT);
mHandler = new Handler(Looper.getMainLooper()) {
int mNotifId;
boolean mNotifActive;
@Override
public void handleMessage(Message inputMessage) {
switch (inputMessage.what) {
case INVALIDATE_VIEW:
//Log.d(TAG, "Invalidate view");
mapView.invalidate();
// Fall-through
case POLL_NOTIFICATIONS:
int dlsb = mG.mTileLayerProvider.getActiveDownloads();
int dlsv = mG.mVectorLayerProvider.getActiveDownloads();
Log.d(TAG, "Downloads active="+dlsb+"+"+dlsv);
if (dlsb>0 || dlsv>0) {
if (! mNotifActive) {
String st = "Downloading...";
mNotifId = setOsmLoadNotif(st);
mNotifActive = true;
} else {
Log.d(TAG, "Notification already active");
mHandler.sendMessageDelayed(mHandler.obtainMessage(POLL_NOTIFICATIONS, this), 1000);
}
} else if (mNotifActive) {
cancelOsmLoadNotif(mNotifId);
mNotifActive = false;
}
break;
}
}
};
mapView = new MapView(this);
//mG.mPcfg.update(getBaseContext());
setContentView(mapView);
mapView.requestFocus();
if (getLastNonConfigurationInstance() != null) {
Log.d(TAG, "getLastNonConfigurationInstance() != null");
mG = (SharedData) getLastNonConfigurationInstance();
mapView.setSharedData(mG);
mG.mTileLayer.setMainHandler(mHandler);
mG.mVectorLayer.setMainHandler(mHandler);
mG.update(getApplicationContext());
} else {
long maxMemL = Runtime.getRuntime().maxMemory();
int maxMem = (int) Math.min(maxMemL, Integer.MAX_VALUE);
Log.i(TAG, "maxMemory="+maxMem);
mG = new SharedData();
mapView.setSharedData(mG);
mG.mTileLayerProvider = new OsmTileProvider(mG.mOsmServerAgentName, OsmTile.MAX_DOWNLOAD_THREADS);
mG.mTileLayer = new OsmTileLayerBm(mG.mTileLayerProvider, maxMem/4);
mG.mTileLayer.setAttrib(getApplicationContext().getString(R.string.info_osm_copyright));
mG.mTileLayer.setMainHandler(mHandler);
mG.mTileLayer.setProviderUrl(OsmTileLayer.urlFromType(mG.mPcfg.mBackMapType));
mG.mVectorLayerProvider = new OsmTileProvider(mG.mOsmServerAgentName);
mG.mVectorLayer = new OsmTileLayerVector(mG.mVectorLayerProvider, maxMem/8);
mG.mVectorLayer.setSharedData(mG);
mG.mVectorLayer.setMainHandler(mHandler);
mG.update(getApplicationContext());
}
if (mG.mDeveloperMode) {
if(!(Thread.getDefaultUncaughtExceptionHandler() instanceof CustomExceptionHandler)) {
Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(mG, ""));
}
}
mGestureDetector = new GestureDetectorCompat(this, mGestureListener);
mScaleGestureDetector = new ScaleGestureDetector(this, mScaleGestureListener);
/*mDetector.setOnDoubleTapListener(this);*/
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
mLocationManager.addGpsStatusListener(this);
Location loc = getMostRecentKnownLocation();
if (loc != null)
this.onLocationChanged(loc);
mLocationClient = new LocationClient(this, this, this);
mLocationRequest = LocationRequest.create();
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setInterval(1000/*ms*/);
mLocationRequest.setFastestInterval(500/*ms*/);
//locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
//if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
// Intent intent = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
// startActivity(intent);
//}
//Criteria criteria = new Criteria();
//mG.mLocProvider = locationManager.getBestProvider(criteria, false);
//mG.mLocProvider = locationManager.GPS_PROVIDER;
//Location location = locationManager.getLastKnownLocation(mG.mLocProvider);
//if (location != null) {
// Toast toast = Toast.makeText(getApplicationContext(), "Location provider "+mG.mLocProvider, Toast.LENGTH_SHORT);
// toast.show();
// //onLocationChanged(location);
//}
//Location location = new Location("FIXME");
//Location location = mLocationClient.getLastLocation();
if (mG.mUseCompass) {
sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
sensorAccelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
sensorMagneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
valuesAccelerometer = new float[3];
valuesMagneticField = new float[3];
matrixR = new float[9];
matrixI = new float[9];
matrixValues = new float[3];
}
// Initialize the location fields
//if (location != null) {
// Log.d(TAG, "Provider " + mG.mLocProvider + " has been selected.");
// onLocationChanged(location);
//} else {
//latituteField.setText("Location not available");
//longitudeField.setText("Location not available");
//}
//registerForContextMenu(mapView);
registerComponentCallbacks(new ComponentCallbacks2() {
@ Override
public void onTrimMemory(int level) {
appTrimMemory(level);
}
public void onLowMemory() {
appTrimMemory(TRIM_MEMORY_COMPLETE);
}
public void onConfigurationChanged(Configuration newConfig) {
//
}
});
}
public void appTrimMemory(int level) {
Log.d(TAG, "appTrimMemory("+level+")");
if (mG.mTileLayer != null)
mG.mTileLayer.onTrimMemory(level);
if (mG.mVectorLayer != null)
mG.mVectorLayer.onTrimMemory(level);
}
public Object onRetainNonConfigurationInstance() {
if (mG != null)
return mG;
return super.onRetainNonConfigurationInstance();
}
/*
* Called when the Activity becomes visible.
*/
@Override
protected void onStart() {
super.onStart();
mLocationClient.connect();
}
/*
* Called when the Activity is no longer visible.
*/
@Override
protected void onStop() {
if (mLocationClient.isConnected()) {
mLocationClient.removeLocationUpdates(this);
}
mLocationClient.disconnect();
super.onStop();
}
// Define a DialogFragment that displays the error dialog
public static class ErrorDialogFragment extends DialogFragment {
// Global field to contain the error dialog
private Dialog mDialog;
// Default constructor. Sets the dialog field to null
public ErrorDialogFragment() {
super();
mDialog = null;
}
// Set the dialog to display
public void setDialog(Dialog dialog) {
mDialog = dialog;
}
// Return a Dialog to the DialogFragment.
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return mDialog;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case CONNECTION_FAILURE_RESOLUTION_REQUEST:
/*
* If the result code is Activity.RESULT_OK, try
* to connect again
*/
switch (resultCode) {
case Activity.RESULT_OK:
break;
}
case PREFERENCE_REQUEST:
mG.update(getBaseContext());
mapView.postInvalidate();
break;
}
}
private boolean servicesConnected() {
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (ConnectionResult.SUCCESS == resultCode) {
Log.d("Location Updates", "Google Play services is available.");
return true;
} else {
//int errorCode = GooglePlayServicesUtil.getErrorCode();
Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(resultCode, this,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
if (errorDialog != null) {
errorDialog.show();
} else {
showOkDialog(this, "Something went wrong with Google Play Services");
}
return false;
}
}
public static void showOkDialog(Context context, String txt)
{
Builder builder = new AlertDialog.Builder(context);
builder.setMessage(txt);
builder.setCancelable(true);
builder.setPositiveButton("OK", null);
AlertDialog dialog = builder.create();
dialog.show();
}
@Override
public void onConnected(Bundle dataBundle) {
Log.d(TAG, "onConnected");
mLocationClient.requestLocationUpdates(mLocationRequest, this);
}
@Override
public void onDisconnected() {
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
if (connectionResult.hasResolution()) {
try {
// Start an Activity that tries to resolve the error
connectionResult.startResolutionForResult(
this,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
/*
* Thrown if Google Play services canceled the original
* PendingIntent
*/
} catch (IntentSender.SendIntentException e) {
// Log the error
e.printStackTrace();
}
} else {
/*
* If no resolution is available, display a dialog to the
* user with the error.
*/
//showErrorDialog(connectionResult.getErrorCode());
}
}
@Override
public boolean onTouchEvent(MotionEvent event){
boolean h = mScaleGestureDetector.onTouchEvent(event);
h |= mGestureDetector.onTouchEvent(event);
h |= super.onTouchEvent(event);
return h;
}
private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener
= new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
float focusX = scaleGestureDetector.getFocusX();
float focusY = scaleGestureDetector.getFocusY();
Log.d(TAG, "ScaleBegin, focus="+focusX+","+focusY);
return true;
}
@Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
float focusX = scaleGestureDetector.getFocusX();
float focusY = scaleGestureDetector.getFocusY();
float scale = scaleGestureDetector.getScaleFactor();
Log.d(TAG, "Scale, scale="+scale+", focus="+focusX+","+focusY);
mG.mPcfg.setScale(mG.mPcfg.getScale()*scale, mG.mCtx);
mScaleInProgress = true;
mapView.postInvalidate();
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
Log.d(TAG, "ScaleEnd");
}
};
private final GestureDetector.SimpleOnGestureListener mGestureListener
= new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDown(MotionEvent event) {
//Log.d(TAG,"onDown: " + event.toString());
mPanLon = mG.mLon; mPanLat = mG.mLat;
mScaleInProgress = false;
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
if (mScaleInProgress) {
Log.d(TAG, "Scroll skipped");
return false;
}
//Log.d(TAG, "onScroll: " + e1.toString()+e2.toString());
float dx, dy;
dx = e2.getX()-e1.getX();
dy = e2.getY()-e1.getY();
Log.d(TAG, "Scroll pixels: x="+dx+" y="+dy);
mG.mLon = mPanLon - dx/(mG.mPcfg.mScale*MapView.prescale);
double panMercLat = dy/(mG.mPcfg.mScale*MapView.prescale);
mG.mLat = GeoMath.mercatorToLat((GeoMath.latToMercator(mPanLat)+panMercLat));
Log.d(TAG, "Scroll to lon="+mG.mLon+" lat="+mG.mLat+" ("+GeoMath.latToMercator(mPanLat)+"+"+panMercLat+")");
mG.mFollowGPS = false;
mapView.postInvalidate();
return true;
}
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
Log.d(TAG, "onFling: " + event1.toString()+event2.toString());
return true;
}
// @Override
// public void onLongPress(MotionEvent event) {
// Log.d(TAG, "onLongPress: " + event.toString());
// openOptionsMenu();
// }
//@Override
//public void onShowPress(MotionEvent event) {
// Log.d(TAG, "onShowPress: " + event.toString());
//}
//@Override
//public boolean onSingleTapUp(MotionEvent event) {
// Log.d(TAG, "onSingleTapUp: " + event.toString());
// return true;
// }
};
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_activity_actions, menu);
if (mG.mDeveloperMode) {
menu.add(0, R.id.action_testdownload, Menu.NONE, R.string.action_testdownload);
menu.add(0, R.id.action_togglehud, Menu.NONE, R.string.action_togglehud);
}
return super.onCreateOptionsMenu(menu);
}
//@Override
//public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
// super.onCreateContextMenu(menu, v, menuInfo);
// getMenuInflater().inflate(R.menu.main, menu);
//}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.action_download) {
if (mG.mPhyLocation != null) {
Log.d(TAG, "Download");
//float horizon = 250f; //meters
//GeoBBox bbox = OsmServer.getBoxForPoint(mG.mLat, mG.mLon, horizon);
//GeoBBox bbox = OsmTile.pos2BBox(mG.mLon, mG.mLat, 16);
//downloadBox(bbox);
// FIXME
mG.mVectorLayer.download(mG.mLon, mG.mLat);
} else {
Toast toast = Toast.makeText(getApplicationContext(), getBaseContext().getString(R.string.info_locationunknown), Toast.LENGTH_SHORT);
toast.show();
}
return true;
// } else if (itemId == R.id.action_testdownload) {
// Log.d(TAG, "Test Download");
// float horizon = 250f; //meters
// GeoBBox bbox = OsmServer.getBoxForPoint(mG.mLat, mG.mLon, horizon);
// olddownloadBox(bbox);
// return true;
// } else if (itemId == R.id.action_loadcache) {
// Log.d(TAG, "Load Cache");
// readOSMFile("cache.osm");
// return true;
} else if (itemId == R.id.action_whereami) {
Log.d(TAG, "Where am I?");
mG.mFollowGPS = true;
mG.mPcfg.setScale(PaintConfig.DEFAULT_ZOOM, mG.mCtx);
if (mG.mPhyLocation != null) {
mG.mLat = mG.mPhyLocation.getLatitude();
mG.mLon = mG.mPhyLocation.getLongitude();
mapView.postInvalidate();
} else {
Toast toast = Toast.makeText(getApplicationContext(), getBaseContext().getString(R.string.info_locationunknown), Toast.LENGTH_SHORT);
toast.show();
}
return true;
} else if (itemId == R.id.action_togglehud) {
mG.mDebugHUD = !mG.mDebugHUD;
mapView.invalidate();
return true;
} else if (itemId == R.id.action_settings) {
startActivityForResult(new Intent(this, SettingsActivity.class), PREFERENCE_REQUEST);
mG.update(getApplicationContext());
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onResume() {
super.onResume();
//locationManager.requestLocationUpdates(mG.mLocProvider, 1000/*ms*/, 1/*meters*/, this);
if (mG.mUseCompass) {
sensorManager.registerListener(this,
sensorAccelerometer,
SensorManager.SENSOR_DELAY_NORMAL);
sensorManager.registerListener(this,
sensorMagneticField,
SensorManager.SENSOR_DELAY_NORMAL);
}
}
@Override
protected void onPause() {
super.onPause();
if (mG.mUseCompass) {
sensorManager.unregisterListener(this,
sensorAccelerometer);
sensorManager.unregisterListener(this,
sensorMagneticField);
}
}
protected Location getMostRecentKnownLocation() {
Location loc = null;
long besttime = 0;
List<String> allp = mLocationManager.getAllProviders();
for (String p: allp) {
Location l = mLocationManager.getLastKnownLocation(p);
if (l != null) {
long time = l.getTime();
if (time>besttime) {
loc = l;
besttime = time;
}
}
}
return loc;
}
@Override
public void onLocationChanged(Location location) {
mG.mPhyLocation = location;
mG.mLocationUpdates++;
double curr_lon = mG.mPhyLocation.getLongitude();
double curr_lat = mG.mPhyLocation.getLatitude();
if (mG.mFollowGPS) {
mG.mLon = curr_lon;
mG.mLat = curr_lat;
}
//Log.d(TAG, "onLocationChanged, Loc="+mG.mPhyLocation);
final double noise = 0.00002;
if (Math.abs(mLonLastUpd-curr_lon)>noise || Math.abs(mLatLastUpd-curr_lat)>noise) {
mLonLastUpd = curr_lon;
mLatLastUpd = curr_lat;
mapView.postInvalidate();
}
}
public void onGpsStatusChanged(int event) {
boolean statchg = false;
switch (event) {
case GpsStatus.GPS_EVENT_SATELLITE_STATUS:
GpsStatus gstat = mLocationManager.getGpsStatus(null);
if (gstat != null) {
Iterable<GpsSatellite> satellites = gstat.getSatellites();
Iterator<GpsSatellite> sat = satellites.iterator();
int num = 0, used = 0;
while (sat.hasNext()) {
num++;
GpsSatellite satellite = sat.next();
if (satellite.usedInFix()) {
used++;
}
}
if (mG.mSatelitesVisible != num || mG.mSatelitesUsed != used)
statchg = true;
mG.mSatelitesVisible = num;
mG.mSatelitesUsed = used;
}
break;
}
if (statchg)
mapView.postInvalidate();
}
@Override
public void onSensorChanged(SensorEvent event) {
switch(event.sensor.getType()){
case Sensor.TYPE_ACCELEROMETER:
for(int i =0; i < 3; i++){
valuesAccelerometer[i] = event.values[i];
}
break;
case Sensor.TYPE_MAGNETIC_FIELD:
for(int i =0; i < 3; i++){
valuesMagneticField[i] = event.values[i];
}
break;
}
boolean success = SensorManager.getRotationMatrix(
matrixR,
matrixI,
valuesAccelerometer,
valuesMagneticField);
if(success){
SensorManager.getOrientation(matrixR, matrixValues);
mG.mAzimuth = Math.toDegrees(matrixValues[0]);
mG.mPitch = Math.toDegrees(matrixValues[1]);
mG.mRoll = Math.toDegrees(matrixValues[2]);
//Log.d(TAG, "Orientation: Azimuth " + azimuth + ", Pitch " + pitch + ", Roll " + roll);
mapView.postInvalidate();
}
}
int setOsmLoadNotif(String info) {
int notifyID = 1;
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(/*this*/getApplicationContext())
.setContentTitle("Downloading OSM Data")
.setContentText(info)
.setSmallIcon(R.drawable.ic_downloadosm);
// Intent resultIntent = new Intent(/*this*/getApplicationContext(), MainActivity.class); //FIXME
// PendingIntent resultPendingIntent =
// PendingIntent.getActivity(
// /*this*/getApplicationContext(),
// 0,
// resultIntent,
// PendingIntent.FLAG_UPDATE_CURRENT
// );
// mBuilder.setContentIntent(resultPendingIntent);
mNotificationManager.notify(
notifyID,
mBuilder.build());
Log.d(TAG, "Set OSM notif, id="+notifyID);
return notifyID;
}
void cancelOsmLoadNotif(int notifyID) {
Log.d(TAG, "Clear OSM notif, id="+notifyID);
mNotificationManager.cancel(notifyID);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}

View file

@ -0,0 +1,14 @@
package dk.network42.osmfocus;
import android.graphics.Canvas;
import android.graphics.RectF;
abstract public class MapLayer {
static final int MAPTYPE_NONE = 1;
static final int MAPTYPE_OSM = 2;
static final int MAPTYPE_OSMCYCLEMAP = 3;
static final int MAPTYPE_INTERNAL = 4;
abstract public void draw(Canvas canvas, RectF worldport, PaintConfig pcfg);
public void onTrimMemory(int level) {}
}

View file

@ -0,0 +1,165 @@
package dk.network42.osmfocus;
import java.util.ArrayList;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.location.GpsStatus;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
public class MapView extends View {
private static final String TAG = "MapView";
String mLocProvider, mTmpStr;
GpsStatus mGpsStatus;
long mLastRedraw;
SharedData mG;
ArrayList<OsmElement> mNearest = new ArrayList<OsmElement>(10);
Rect viewrect = new Rect();
RectF viewrectf = new RectF();
RectF worldrect = new RectF();
int mRedraws=0;
Context context;
static public final float prescale = 150000.0f;
public MapView(Context ctx) {
super(ctx);
setFocusable(true);
setFocusableInTouchMode(true);
context = ctx;
mLastRedraw = SystemClock.elapsedRealtime();
}
@Override
public void onDraw(Canvas canvas) {
long now = SystemClock.elapsedRealtime();
canvas.drawARGB(255, 238, 241, 233);
canvas.getClipBounds(viewrect);
int h = viewrect.height();
int w = viewrect.width();
viewrectf.set(viewrect);
//Log.d(TAG, "Viewrectf: "+viewrectf.toString());
mG.mPcfg.viewmatrix.reset();
canvas.save();
//canvas.getMatrix().mapRect(viewrectf);
//Log.d(TAG, "2 Viewrectf: "+viewrectf.toString());
canvas.translate(w/2, h/2);
mG.mPcfg.viewmatrix.preTranslate(w/2, h/2);
if (mG.mUseCompass) {
canvas.rotate((float)-mG.mAzimuth);
}
canvas.save();
canvas.scale(1.0f, -1.0f); // Up is down
mG.mPcfg.viewmatrix.preScale(1.0f, -1.0f);
canvas.scale(mG.mPcfg.mScale, mG.mPcfg.mScale); // Current zoom
mG.mPcfg.viewmatrix.preScale(mG.mPcfg.mScale, mG.mPcfg.mScale);
canvas.translate((float)(-mG.mLon*MapView.prescale), (float)(-GeoMath.latToMercator(mG.mLat)*MapView.prescale)); // Current pos
mG.mPcfg.viewmatrix.preTranslate((float)(-mG.mLon*MapView.prescale), (float)(-GeoMath.latToMercator(mG.mLat)*MapView.prescale)); // Current pos
worldrect.set(viewrectf);
mG.mPcfg.view2world(worldrect);
//Log.d(TAG, "Viewport="+viewrectf+", world viewport="+worldrect);
if (mG.mLocationUpdates > 0) {
if (mG.mTileLayer != null && (mG.mPcfg.mBackMapType == MapLayer.MAPTYPE_OSM || mG.mPcfg.mBackMapType == MapLayer.MAPTYPE_OSMCYCLEMAP)) {
mG.mTileLayer.draw(canvas, worldrect, mG.mPcfg);
}
if (mG.mVectorLayer != null) {
mG.mVectorLayer.draw(canvas, worldrect, mG.mPcfg);
}
}
// GPS position
if (mG.mPhyLocation != null) {
canvas.drawCircle((float)(mG.mPhyLocation.getLongitude()*MapView.prescale),
(float)(GeoMath.latToMercator(mG.mPhyLocation.getLatitude())*MapView.prescale),
(float)(GeoMath.convertMetersToGeoDistanceMed((float)mG.mPhyLocation.getAccuracy())*MapView.prescale), mG.mPcfg.gps);
}
canvas.restore(); // Restores zoom scaling, current position translation and 'up is down'
if (mG.mPhyLocation == null) {
String txt = context.getString(R.string.info_locationunknown);
canvas.drawText(txt, -mG.mPcfg.debughud.measureText(txt)/2, -3*mG.mPcfg.tagtextsize, mG.mPcfg.debughud);
txt = "Satelites: Visible:"+mG.mSatelitesVisible+" used:"+mG.mSatelitesUsed;
canvas.drawText(txt, -mG.mPcfg.debughud.measureText(txt)/2, -2*mG.mPcfg.tagtextsize, mG.mPcfg.debughud);
} else {
// Compass
// canvas.save();
// canvas.translate(w/2, h/2);
// canvas.scale(w/2, h/2); //FIXME
// //canvas.drawLine(0, 0, FloatMath.cos(aarad), FloatMath.sin(aarad), mG.mPcfg.paint);
// //canvas.drawCircle(FloatMath.cos(aarad), FloatMath.sin(aarad), 5, mG.mPcfg.paint);
// //canvas.drawCircle(0, 0, 1, mG.mPcfg.paint);
// Cross-hair
float len1 = 2.0f*mG.mPcfg.densityScale;
float len2 = 10.0f*mG.mPcfg.densityScale;
canvas.drawLine(0, -len1, 0, -len2, mG.mPcfg.gps2);
canvas.drawLine(0, len1, 0, len2, mG.mPcfg.gps2);
canvas.drawLine(-len1, 0, -len2, 0, mG.mPcfg.gps2);
canvas.drawLine( len1, 0, len2, 0, mG.mPcfg.gps2);
}
canvas.restore(); // Restores center (0,0)
// Tags
if (mG.mLocationUpdates > 0) {
if (mG.mTileLayer != null) {
mG.mTileLayer.drawLegends(canvas, viewrectf, mG.mPcfg);
}
if (mG.mVectorLayer != null) {
mG.mVectorLayer.drawLegends(canvas, viewrectf, mG.mPcfg);
}
}
canvas.restore();
long endtime = SystemClock.elapsedRealtime();
// Debug info
float dbghudY = h/2;
if (mG.mDebugHUD) {
canvas.drawText(String.format("View Loc: %.8f,%.8f, upd %d, sats %d/%d, scale %.4f",
mG.mLon, mG.mLat, mG.mLocationUpdates, mG.mSatelitesVisible, mG.mSatelitesUsed, mG.mPcfg.mScale),
0, dbghudY+mG.mPcfg.mHUDspacing, mG.mPcfg.debughud);
if (mG.mPhyLocation != null) {
canvas.drawText(String.format("Phy Loc: %.8f,%.8f, Accuracy: %.4f (%s)",
mG.mPhyLocation.getLongitude(), mG.mPhyLocation.getLatitude(), mG.mPhyLocation.getAccuracy(), mLocProvider),
0, dbghudY+2*mG.mPcfg.mHUDspacing, mG.mPcfg.debughud);
} else {
canvas.drawText("No phy location yet",
0, dbghudY+2*mG.mPcfg.mHUDspacing, mG.mPcfg.debughud);
}
canvas.drawText(String.format("Azimuth: %.4f, Pitch: %.4f, Roll: %.4f", mG.mAzimuth, mG.mPitch, mG.mRoll),
0, dbghudY+3*mG.mPcfg.mHUDspacing, mG.mPcfg.debughud);
if (mG.mDb != null) {
canvas.drawText(String.format("Nodes: %d, Ways: %d, Relations: %d",
mG.mDb.getNodes().size(), mG.mDb.getWays().size(), mG.mDb.getRelations().size()),
0, dbghudY+4*mG.mPcfg.mHUDspacing, mG.mPcfg.debughud);
}
canvas.drawText(String.format("RedrawPeriod: %dms, RedrawTime: %dms, Redraws:%d",
now-mLastRedraw, endtime-now, ++mRedraws), 0, dbghudY+5*mG.mPcfg.mHUDspacing, mG.mPcfg.debughud);
int memTile=0, memXml=0;
if (mG.mTileLayer!=null)
memTile = mG.mTileLayer.getMemorySize();
if (mG.mVectorLayer!=null)
memXml = mG.mVectorLayer.getMemorySize();
canvas.drawText(String.format("MemoryUse: Tiles=%dk, Vector: %dk",
memTile/1024, memXml/1024), 0, dbghudY+6*mG.mPcfg.mHUDspacing, mG.mPcfg.debughud);
}
//Log.d(TAG, "h="+h+"spacing="+mG.mPcfg.mListSpacing);
mLastRedraw = now;
}
public void setSharedData(SharedData data) {
mG = data;
}
}

View file

@ -0,0 +1,45 @@
package dk.network42.osmfocus;
import android.graphics.Canvas;
public class OsmBounds {
private static final String TAG = "OsmBounds";
public
OsmNode min = null, max = null;
public boolean isEmpty() {
return (min==null || max==null);
}
public OsmBounds() {
}
public OsmBounds(int minlon, int minlat, int maxlon, int maxlat) {
// Normalize by swapping
int tmp;
if (minlon>maxlon) {
tmp = minlon;
minlon = maxlon;
maxlon = tmp;
}
if (minlat>maxlat) {
tmp = minlat;
minlat = maxlat;
maxlat = tmp;
}
min = new OsmNode(minlon, minlat);
max = new OsmNode(maxlon, maxlat);
}
// Osm geopoints, lon/lat * 1e7
int getLeft() { return min.getOsmLon(); }
int getTop() { return min.getOsmLat(); }
int getRight() { return max.getOsmLon(); }
int getBottom() { return max.getOsmLat(); }
public void draw(Canvas canvas, OsmDB db, PaintConfig pcfg) {
canvas.drawRect(min.getX(db), min.getY(db), max.getX(db), max.getY(db), pcfg.bounds);
}
}

View file

@ -0,0 +1,396 @@
package dk.network42.osmfocus;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.util.Log;
public class OsmDB extends MapLayer {
private static final String TAG = "OsmDB";
private long mWayId = 0;
private long mNodeId = 0;
private Map<Long, OsmNode> mNodes;
private Map<Long, OsmWay> mWays;
private Map<Long, OsmRelation> mRels;
private ArrayList<OsmBounds> mBounds;
private Filter mFilter = new Filter();
float mX, mY, mYmerc; // Current view position, latitude is Mercator corrected
OsmDB() {
mNodes = new HashMap<Long, OsmNode>();
mWays = new HashMap<Long, OsmWay>();
mRels = new HashMap<Long, OsmRelation>();
mBounds = new ArrayList<OsmBounds>();
}
public OsmBounds getBounds() { // FIXME: Not really correct with multiple bounds
return mBounds.get(0);
}
//void setBBox(final GeoBBox bbox) {
// this.box = bbox;
//}
// True if we have any bounds defined. May contain zero nodes/ways
public boolean isEmpty() {
return (mBounds.isEmpty() || mBounds.get(0).isEmpty());
}
public OsmNode getNode(final long osmId) {
return mNodes.get(osmId);
}
public OsmWay getWay(final long osmId) {
return mWays.get(osmId);
}
public OsmWay getRelation(final long osmId) {
return mWays.get(osmId);
}
public OsmElement getElement(final long osmId) {
OsmElement elem = mWays.get(osmId);
if (elem != null)
return elem;
elem = mNodes.get(osmId);
if (elem != null)
return elem;
elem = mRels.get(osmId);
if (elem != null)
return elem;
return null;
}
public Map<Long, OsmNode> getNodes() {
return mNodes;
}
public Map<Long, OsmWay> getWays() {
return mWays;
}
public Map<Long, OsmRelation> getRelations() {
return mRels;
}
void insertNode(final OsmNode node) {
mNodes.put(node.mOsmId, node);
node.mDb = this;
}
void insertWay(final OsmWay way) {
mWays.put(way.mOsmId, way);
way.mDb = this;
}
void insertRelation(final OsmRelation rel) {
mRels.put(rel.mOsmId, rel);
rel.mDb = this;
}
void insertBounds(final OsmBounds box) {
mBounds.add(box);
if (mBounds.size()==1) {
mX = (float) (getBounds().getLeft()/1e7f);
mY = (float) (getBounds().getTop()/1e7f);
mYmerc = (float) GeoMath.latToMercator(mY);
//Log.d(TAG, "DB ref frozen to "+mX+", "+mY+"("+mYmerc+")");
}
}
public static OsmNode createNode(long osmId, long osmVersion, int lat, int lon) {
return new OsmNode(osmId, osmVersion, lat, lon);
}
public OsmNode createNodeWithNewId(int lat, int lon) {
return createNode(--mNodeId, 1, lat, lon);
}
public static OsmWay createWay(long osmId, long osmVersion) {
return new OsmWay(osmId, osmVersion);
}
public OsmWay createWayWithNewId() {
return createWay(--mWayId, 1);
}
public static OsmRelation createRelation(long osmId, long osmVersion) {
return new OsmRelation(osmId, osmVersion);
}
public OsmRelation createRelationWithNewId() {
return createRelation(--mWayId, 1);
}
public static OsmBounds createBounds(int minlon, int minlat, int maxlon, int maxlat) throws OsmException {
return new OsmBounds(minlon, minlat, maxlon, maxlat);
}
// FIXME handle overlaps, merge newest
void merge(OsmDB with) {
if (with==null)
return;
if (mNodes.size()==0)
mNodes = with.mNodes;
if (mWays.size()==0)
mWays = with.mWays;
with.mX = mX; // Update with relative origin of dest db
with.mY = mY;
with.mYmerc = mYmerc;
with.updateVisuals();
Iterator it;
it = with.mNodes.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
if (!mNodes.containsKey(pair.getKey())) {
insertNode((OsmNode)pair.getValue());
}
}
it = with.mWays.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
if (!mWays.containsKey(pair.getKey())) {
insertWay((OsmWay)pair.getValue());
}
}
}
void updateAndVacuum() {
Iterator it = mWays.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
OsmWay ee = (OsmWay)pair.getValue();
mFilter.filter(ee);
Iterator nit = ee.getNodes().iterator();
while (nit.hasNext()) {
OsmNode nn = (OsmNode) nit.next();
nn.mIsWayNode = true;
}
}
it = mNodes.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
mFilter.filter((OsmElement)pair.getValue());
}
updateAndVacuumRelations();
vacuumWays();
updateVisuals();
}
// Remove relation that we do not use
void updateAndVacuumRelations() {
Iterator it = mRels.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
OsmRelation rel = (OsmRelation) pair.getValue();
if (rel.hasTag("type", "multipolygon")) {
rel.updateVisualDefiningElem();
} else {
//Log.d(TAG, "Vacuum Rel, id="+rel.mOsmId);
it.remove();
}
}
}
// Remove e.g. ways tagged with building=yes and which are part of a multipolygon with role=outer
void vacuumWays() {
Iterator it = mRels.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
OsmRelation rel = (OsmRelation) pair.getValue();
if (rel.hasTag("type", "multipolygon") && rel.hasRole("outer")) {
long ref = rel.getRoleRef("outer");
//Log.d(TAG, "Rel id="+rel.mOsmId+", outer id="+ref);
OsmWay way = mWays.get(ref);
if (way != null) {
way.mIsMultipolyOuter = true;
}
}
}
}
void updateVisuals() {
Iterator it = mWays.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
OsmWay way = (OsmWay) pair.getValue();
way.updateVisuals(this);
}
it = mRels.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
OsmRelation rel = (OsmRelation) pair.getValue();
rel.updateVisuals(this);
}
}
public static boolean idIsInList(long id, ArrayList<OsmElement> list) {
for (int ii = 0; ii<list.size(); ii++) {
if (list.get(ii).getOsmId() == id)
return true;
}
return false;
}
public int closestElements(double lon, double lat, int max, ArrayList<OsmElement> near) {
double lat_merc = GeoMath.latToMercator(lat);
//Log.d(TAG, "Find elements close to ("+lon+","+lat+"(merc="+lat_merc+"))");
OsmElement e;
Map.Entry pair;
Iterator it = mNodes.entrySet().iterator();
while (it.hasNext()) {
pair = (Map.Entry) it.next();
e = (OsmNode) pair.getValue();
if (e.getTagCnt() > 0 && !e.mFiltered && !idIsInList(e.getOsmId(), near)) {
e.compareSetDistTo(lon, lat_merc);
if (near.size() < max) {
near.add(e);
//Log.d(TAG, "Node(Id="+e.mOsmId+"): Add, near size="+near.size()+" near=["+dumpNearArray(near)+"]");
Collections.sort(near);
} else if (near.get(near.size()-1).compareGet() > e.compareGet()) {
near.set(near.size()-1, e);
Collections.sort(near);
}
//Log.d(TAG, "Node(Id="+e.mOsmId+"): near size="+near.size()+" near=["+dumpNearArray(near)+"]");
}
}
it = mWays.entrySet().iterator();
while (it.hasNext()) {
pair = (Map.Entry) it.next();
e = (OsmWay) pair.getValue();
if (e.getTagCnt() > 0 && !e.mFiltered && !idIsInList(e.getOsmId(), near)) {
e.compareSetDistTo(lon, lat_merc);
if (near.size() < max) {
near.add(e);
//Log.d(TAG, "Way(Id="+e.mOsmId+"): Add, near size="+near.size()+" near=["+dumpNearArray(near)+"]");
Collections.sort(near);
} else if (near.get(near.size()-1).compareGet() > e.compareGet()) {
near.set(near.size()-1, e);
//Log.d(TAG, "Way(Id="+e.mOsmId+"): insert at "+(near.size()-1)+" near=["+dumpNearArray(near)+"]");
Collections.sort(near);
}
}
}
//Log.d(TAG, "Final near=["+dumpNearArray(near)+"]");
return 1;
}
// private static String dumpNearArray(ArrayList<OsmElement> near) {
// String st = "";
// for (int ii=0; ii<near.size(); ii++) {
// st += near.get(ii).mOsmId + "(C="+near.get(ii).compareGet()+") ";
// }
// return st;
//}
// private static String dumpNearArray(ArrayList<OsmElement> near) {
// String st = "";
// for (int ii=0; ii<near.size(); ii++) {
// st += " x="+near.get(ii).getX()+" y="+near.get(ii).getY();
// }
// return st;
// }
public static void quadSort(ArrayList<OsmElement> near) {
Collections.sort(near, OsmElement.Compare.Y_COORD_DESCENDING);
if (near.size()==4) {
Collections.sort(near.subList(0, 2), OsmElement.Compare.X_COORD);
Collections.sort(near.subList(2, 4), OsmElement.Compare.X_COORD);
}
if (near.size()==8) {
Collections.sort(near.subList(0, 3), OsmElement.Compare.X_COORD);
Collections.sort(near.subList(3, 5), OsmElement.Compare.X_COORD);
Collections.sort(near.subList(5, 8), OsmElement.Compare.X_COORD);
}
//Log.d(TAG, "After sort:"+dumpNearArray(near));
}
// Clock-wise
public static void radianSortCW(ArrayList<OsmElement> near, double lon, double latMerc, double angleOffset) {
for (int ii = 0; ii<near.size(); ++ii) {
near.get(ii).compareSetAngleTo(lon, latMerc, angleOffset);
}
//Log.d(TAG, "Before sort:"+dumpNearArray(near));
Collections.sort(near, new Comparator<OsmElement>(){
@Override
public int compare(OsmElement e1, OsmElement e2) {
return (int) e2.compareTo(e1);
}
});
//Log.d(TAG, "After sort:"+dumpNearArray(near));
}
// Counter clock-wise
public static void radianSortCCW(ArrayList<OsmElement> near, double lon, double latMerc, double angleOffset) {
for (int ii = 0; ii<near.size(); ++ii) {
near.get(ii).compareSetAngleTo(lon, latMerc, angleOffset);
}
Collections.sort(near);
}
public void draw(Canvas canvas, RectF worldport, PaintConfig pcfg) {
if (! isEmpty()) {
canvas.save();
//Log.d(TAG, "DB ref coords lon="+mX+", lat="+mYmerc+"(merc)");
canvas.translate(mX*MapView.prescale, mYmerc*MapView.prescale);
// Elements
int layers = 3;
for (int layer=0; layer<layers; layer++) {
pcfg.setLayer(layer);
Iterator it = getNodes().entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
((OsmNode)pair.getValue()).draw(canvas, this, pcfg);
}
it = getWays().entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
((OsmWay)pair.getValue()).draw(canvas, this, pcfg);
}
it = getRelations().entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
((OsmRelation)pair.getValue()).draw(canvas, this, pcfg);
}
}
canvas.restore();
}
}
public void drawBounds(Canvas canvas, RectF worldport, PaintConfig pcfg) {
canvas.save();
canvas.translate(mX*MapView.prescale, mYmerc*MapView.prescale);
int size = mBounds.size();
for (int ii=0; ii<size; ii++) { // FIXME
mBounds.get(ii).draw(canvas, this, pcfg);
}
canvas.restore();
}
public void highlightElem(Canvas canvas, PaintConfig pcfg, OsmElement elem, int style) {
canvas.save();
canvas.translate(mX*MapView.prescale, mYmerc*MapView.prescale);
elem.highlight(canvas, this, pcfg, style);
canvas.restore();
}
// public void drawElem(Canvas canvas, PaintConfig pcfg, OsmElement elem) {
// canvas.save();
// Log.d(TAG, "DB ref coords lon="+mX+", lat="+mYmerc+"(merc)");
// canvas.translate(mX, mYmerc);
// elem.draw(canvas, this, pcfg);
// canvas.restore();
// }
public String toString() {
return "DB={"+mNodes.size()+"/"+mWays.size()+"/"+mRels.size()+"}";
}
}

View file

@ -0,0 +1,317 @@
package dk.network42.osmfocus;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.Log;
abstract public class OsmElement implements Comparable<OsmElement> {
private static final String TAG = "OsmElement";
protected long mOsmId;
protected long mOsmVersion;
protected SortedMap<String, String> tags;
protected double mCompare=0; // Cached compare value, only calculate once
boolean mFiltered = false;
boolean mIsArea = false;
OsmDB mDb = null;
enum Compare implements Comparator<OsmElement> {
X_COORD {
public int compare(OsmElement e1, OsmElement e2) {
if (e1.getX() >= e2.getX()) return 1;
else return -1;
}
},
X_COORD_DESCENDING {
public int compare(OsmElement e1, OsmElement e2) {
if (e2.getX() >= e1.getX()) return 1;
else return -1;
}
},
Y_COORD {
public int compare(OsmElement e1, OsmElement e2) {
if (e1.getY() >= e2.getY()) return 1;
else return -1;
}
},
Y_COORD_DESCENDING {
public int compare(OsmElement e1, OsmElement e2) {
if (e2.getY() >= e1.getY()) return 1;
else return -1;
}
},
}
OsmElement(final long id, final long version) {
this.mOsmId = id;
this.mOsmVersion = version;
this.tags = new TreeMap<String, String>();
}
void addOrUpdateTag(final String tag, final String value) {
tags.put(tag, value);
}
public int getTagCnt() {
return tags.size();
}
public boolean hasTag(final String key, final String value) {
String keyValue = tags.get(key);
return keyValue != null && keyValue.equals(value);
}
public String getTagWithKey(final String key) {
return tags.get(key);
}
public boolean hasTagKey(final String key) {
return getTagWithKey(key) != null;
}
public boolean tagsIdentical(OsmElement other) {
if (getTagCnt() != other.getTagCnt())
return false;
for(Map.Entry<String,String> entry : tags.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
String othertag = other.getTagWithKey(key);
if (othertag == null || value.equalsIgnoreCase(othertag))
return false;
}
return true;
}
public long getOsmId() {
return mOsmId;
}
public String toString() {
return "OsmId="+mOsmId;
}
void updateVisuals(OsmDB db) {
/* See also notes on http://wiki.openstreetmap.org/wiki/The_Future_of_Areas */
mIsArea = hasTag("area", "yes") || hasTagKey("landuse") || hasTagKey("building") || hasTagKey("natural");
}
public boolean tagIsFiltered(String key, String value) {
//return false;
return key.startsWith("kms:") || key.startsWith("osak:") || key.equals("created_by")
|| key.equals("addr:country") || key.equals("addr:postcode") || key.equals("source");
}
// Number of lines a given text needs given a maximum width
protected int getTagTextLines(String text, Paint paint, float maxw, float indentw) {
float w = paint.measureText(text);
if (w<= maxw)
return 1;
return 1 + (int)((w-maxw)/(maxw-indentw)); // FIXME: Approximation, needs char algo
}
protected float formatWrappedText(ArrayList<String> lines, String text, Paint paint, float maxw) {
float w = paint.measureText(text);
if (w <= maxw) {
lines.add(text);
return w;
}
while (!text.isEmpty()) {
int ch = paint.breakText(text, true, maxw, null);
lines.add(text.substring(0, ch));
text = text.substring(ch);
if (!text.isEmpty())
text = "\u21AA "+text;
}
return maxw;
}
// Special headlines where two tags are combined, currently only highwayType : NameValue
protected int headLines() {
int lines = 0;
if (hasTagKey("highway") && hasTagKey("name"))
lines += 1;
//if (hasTagKey("amenity") && hasTagKey("name"))
// lines += 1;
return lines;
}
protected float formatWrappedText(ArrayList<String> lines, Paint paint, float maxw) {
String head = null;
float w = 0.0f;
// Compact headlines like "ValueOfHighway : ValueOfName"
if (hasTagKey("highway") && hasTagKey("name")) {
head = getTagWithKey("highway") + " : '" + getTagWithKey("name") + "'";
w = formatWrappedText(lines, head, paint, maxw);
}
// if (hasTagKey("amenity") && hasTagKey("name")) {
// head = getTagWithKey("amenity") + " : '" + getTagWithKey("name") + "'";
// float tmp = formatWrappedText(lines, head, paint, maxw);
// if (w<tmp)
// w = tmp;
// }
for(Map.Entry<String,String> entry : tags.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (head != null && (key.equalsIgnoreCase("highway") || key.equalsIgnoreCase("name")))
continue;
if (!tagIsFiltered(key, value)) {
String txt = key+" = "+value;
float tmp = formatWrappedText(lines, txt, paint, maxw);
if (w<tmp)
w = tmp;
}
}
return w;
}
protected void clipLine(ArrayList<String> lines, int idx, Paint paint, float maxw) {
//final String sym = "\u21a9"; // bend arrow
final String sym = " \u21df";
final String spc = " ";
String txt = lines.get(idx);
float txtw = paint.measureText(txt);
float symw = paint.measureText(sym);
float spcw = paint.measureText(spc);
if (txtw+symw < maxw) {
int spcs = (int)((maxw-(txtw+symw))/spcw);
for (int ii=0; ii<spcs; ii++)
txt += spc;
txt += sym;
} else {
int ch = paint.breakText(txt, true, maxw-symw, null);
txt = txt.substring(0, ch)+sym;
}
lines.set(idx, txt);
}
public RectF drawTags(Canvas canvas, RectF viewrect, boolean poi_lines,
Paint.Align xalign, Paint.Align yalign,
PaintConfig pcfg, int style) {
ArrayList<String> list = new ArrayList<String>();
final int inset = 3; // inset relative to viewbox
final int xborder = 4; int yborder = 4; // margin between text and surrounding box
float x,y, w;
int lines;
float round = 1.0f;
float lspace = pcfg.tag.getTextSize()+1;
float maxx = viewrect.width() * 0.49f;
float maxy = viewrect.height() * 0.49f;
float maxw = maxx-2*xborder-inset;
int maxl = (int)(maxy/lspace);
w = formatWrappedText(list, pcfg.tag, maxw);
lines = list.size();
if (lines>maxl) {
lines=maxl;
clipLine(list, maxl-1, pcfg.tag, maxw);
}
if (xalign == Paint.Align.LEFT) { // x,y is upper left corner of box
x = viewrect.left+inset;
} else if (xalign == Paint.Align.RIGHT) {
x = viewrect.right-w-2*xborder-inset;
} else {
x = (viewrect.right-viewrect.left)/2-w/2-inset;
}
if (yalign == Paint.Align.LEFT) {
y = viewrect.top+inset;
} else if (yalign == Paint.Align.RIGHT) {
y = viewrect.bottom-(2*yborder+lines*lspace)-inset;
} else {
y = (viewrect.bottom-viewrect.top)/2-(lines*lspace)/2-inset;
}
// Draw box
RectF rrect = new RectF(x,y,x+w+2*xborder,y+2*yborder+lines*lspace);
if (headLines() > 0) {
drawFancyFrame(canvas, pcfg.tagback, pcfg.tagback3, rrect, lines-1, lspace);
} else {
canvas.drawRoundRect(rrect, 2*round, 2*round, pcfg.tagback3);
}
canvas.drawRoundRect(rrect, 2*round, 2*round, pcfg.focus2[style]);
// Draw text
for (int ii=0; ii<lines; ii++) {
canvas.drawText(list.get(ii), x+xborder, y+lspace, pcfg.tag);
y += lspace;
}
if (poi_lines) {
// Draw line to point on map
pcfg.pts[0] = getX();
pcfg.pts[1] = getY();
pcfg.viewmatrix.mapPoints(pcfg.pts);
//x = rrect.centerX();
x = xalign == Paint.Align.LEFT ? rrect.right : (xalign == Paint.Align.RIGHT ? rrect.left : viewrect.centerX());
y = yalign == Paint.Align.LEFT ? rrect.bottom : (yalign == Paint.Align.RIGHT ? rrect.top : viewrect.centerY());
double [] newpts = GeoMath.shortenLine(pcfg.pts[0], pcfg.pts[1], x, y, 7.5f*pcfg.densityScale);
//Log.d(TAG, "Tag: pts="+ee.getX()+","+ee.getY()+" screenpts="+pcfg.pts[0]+","+pcfg.pts[1]);
canvas.drawLine(x, y, (float)newpts[0], (float)newpts[1], pcfg.focus2[style]);
}
return rrect;
}
public void drawFancyFrame(Canvas canvas, Paint style1, Paint style2, RectF rrect, int lines, float lspace)
{
float x = rrect.left;
float y = rrect.top;
float w = rrect.right-rrect.left;
float round = 1.0f;
RectF roundrr = new RectF();
Path pp = new Path();
Path pp2 = new Path();
pp.moveTo(x, y+lspace+2); // Top
pp.lineTo(x+w, y+lspace+2);
pp.lineTo(x+w, y+round);
//roundrr.set(rrect.left, rrect.top, rrect.right, rrect.bottom);
roundrr.set(rrect.right-2*round, rrect.top, rrect.right, rrect.top+2*round);
pp.arcTo(roundrr, 0, -90);
pp.lineTo(x+round, y);
roundrr.set(rrect.left, rrect.top, rrect.left+2*round, rrect.top+2*round);
pp.arcTo(roundrr, -90, -90);
canvas.drawPath(pp, style1);
pp2.moveTo(x, y+lspace+2); // Bottom
pp2.lineTo(x+w, y+lspace+2); // Start upper-right
//pp2.lineTo(x+w, y+lines*lspace-round);
roundrr.set(rrect.right-2*round, rrect.bottom-2*round, rrect.right, rrect.bottom);
pp2.arcTo(roundrr, 0, 90);
//pp2.lineTo(x+round, y+lines*lspace);
roundrr.set(rrect.left, rrect.bottom-2*round, rrect.left+2*round, rrect.bottom);
pp2.arcTo(roundrr, 90, 90);
canvas.drawPath(pp2, style2);
}
public void draw(Canvas canvas, OsmDB db, PaintConfig pcfg) {}
public void highlight(Canvas canvas, OsmDB db, PaintConfig pcfg, int style) {}
public abstract double distTo(double lon, double lat_merc);
public abstract double angleTo(double lon, double lat_merc);
// For non-point object these coords are 'close points' - see e.g. compareSetDistTo() for OsmWay
float getX() { return 0; };
float getY() { return 0; };
public int compareTo(OsmElement e) {
// int subtraction does not work with small deltas
if (mCompare >= e.mCompare) {
return 1;
} else {
return -1;
}
}
public void compareSetDistTo(double lon, double lat_merc) { mCompare = distTo(lon, lat_merc); }
public void compareSetAngleTo(double lon, double lat_merc, double offset) { mCompare = angleTo(lon, lat_merc)+offset; }
public double compareGet() { return mCompare; }
}

View file

@ -0,0 +1,15 @@
package dk.network42.osmfocus;
import java.io.IOException;
public class OsmException extends IOException {
public OsmException(final String string) {
super(string);
}
public OsmException(final String string, final Throwable e) {
super(string);
initCause(e);
}
}

View file

@ -0,0 +1,86 @@
package dk.network42.osmfocus;
import android.graphics.Canvas;
public class OsmNode extends OsmElement {
private static final String TAG = "OsmNode";
int mLon, mLat; // OSM geopoint, WGS84 times 1e7
float mLatMerc;
public boolean mIsWayNode = false;
OsmNode(final int lon, final int lat) {
super(0, 0);
mLon = lon;
mLat = lat;
mLatMerc = (float) GeoMath.latToMercator(getLat());
}
OsmNode(final long osmId, final long osmVersion, final int lon, final int lat) {
super(osmId, osmVersion);
mLon = lon;
mLat = lat;
mLatMerc = (float) GeoMath.latToMercator(getLat());
}
public double getLon() { return ((double)mLon)/1e7; }
public double getLat() { return ((double)mLat)/1e7; }
public double getLatMerc() { return mLatMerc; }
public int getOsmLon() { return mLon; }
public int getOsmLat() { return mLat; }
// For screen mapping when db translation is not active
public float getX() { return ((float) getLon())*MapView.prescale; }
public float getY() { return mLatMerc*MapView.prescale; }
// For use when db translation is active
public float getX(OsmDB db) { return ((float) ((getLon()-db.mX)*MapView.prescale)); }
public float getY(OsmDB db) { return ((float) ((mLatMerc-db.mYmerc)*MapView.prescale)); }
//static double getLatMercator(final double lat) { return Math.toDegrees(Math.log(Math.tan(Math.PI/4+Math.toRadians(lat)/2))); }
//public double getLambertLat() {
// return ((double)mLat)/1e7;
//}
//public double getLambertLon() { return ((double)mLon)/1e7; }
// Lambert projection
/*public NodePoint convertToXY(String lat, String lon){
float phi = (float) GPS.radians(Float.parseFloat(lat));
float lambda = (float) GPS.radians(Float.parseFloat(lon));
float q = (float) (2*Math.sin( ((Math.PI/2) - phi)/2 ));
float x = (float)(q*Math.sin(lambda));
float y = (float)(q*Math.cos(lambda));
return new NodePoint(x,y);
}*/
public void draw(Canvas canvas, OsmDB db, PaintConfig pcfg) {
if (! mIsWayNode) {
if (pcfg.getLayer()==1) {
//Log.d(TAG, "Node: xy=("+getX(db)+","+getY(db)+
// "), latlon=("+getLat()+","+getLon()+
// "), DBorig="+db.mX+","+db.mYmerc+
// ", scale="+pcfg.scale);
////canvas.drawCircle(getX(db), getY(db), 2.0f/pcfg.scale, pcfg.focus);
//canvas.drawPoint(getX(db), getY(db), pcfg.focus);
}
}
}
public void highlight(Canvas canvas, OsmDB db, PaintConfig pcfg, int style)
{
// Log.d(TAG, "NodeHighlight: xy=("+getX(db)+","+getY(db)+
// "), latlon=("+getLat()+","+getLon()+
// "), DBorig="+db.mX+","+db.mYmerc);
canvas.drawCircle(getX(db), getY(db), 5.0f*pcfg.densityScale/pcfg.mScale, pcfg.focus[style]);
}
public double distTo(double lon, double lat_merc) {
return Math.hypot(getLon()-lon, getLatMerc()-lat_merc);
}
public double angleTo(double lon, double lat_merc) {
return Math.atan2(getLatMerc()-lat_merc, getLon()-lon);
}
}

View file

@ -0,0 +1,8 @@
package dk.network42.osmfocus;
public class OsmParseException extends Exception {
public OsmParseException(final String msg) {
super(msg);
}
}

View file

@ -0,0 +1,171 @@
package dk.network42.osmfocus;
import java.util.ArrayList;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import android.util.Log;
public class OsmParser extends DefaultHandler {
private static final String TAG = "OsmParser";
private OsmDB mDb;
private OsmNode mCurrentNode;
private OsmWay mCurrentWay;
private OsmRelation mCurrentRel;
private OsmBounds mCurrentBound;
private final ArrayList<Exception> exceptions;
public OsmParser() {
super();
mDb = new OsmDB();
mCurrentNode = null;
mCurrentWay = null;
mCurrentRel = null;
mCurrentBound = null;
exceptions = new ArrayList<Exception>();
}
public OsmDB getStorage() {
return mDb;
}
public void startElement(final String uri, final String name, final String qName, final Attributes atts) {
//Log.d(TAG, "startElement(), name="+name);
try {
if (isOsmElement(name)) {
parseOsmElement(name, atts);
} else if (isWayNode(name)) {
parseWayNode(atts);
} else if (isTag(name)) {
parseTag(atts);
} else if (isRelationMember(name)) {
parseRelationElement(atts);
} else if (isBounds(name)) {
parseBounds(atts);
}
} catch (OsmParseException e) {
Log.e(TAG, "OsmParseException", e);
exceptions.add(e);
}
}
public void endElement(final String uri, final String name, final String qName) {
//Log.d(TAG, "endElement(), name="+name);
if (isNode(name)) {
mDb.insertNode(mCurrentNode);
mCurrentNode = null;
} else if (isWay(name)) {
mDb.insertWay(mCurrentWay);
mCurrentWay = null;
} else if (isRelation(name)) {
mDb.insertRelation(mCurrentRel);
mCurrentRel = null;
} else if (isBounds(name)) {
mDb.insertBounds(mCurrentBound);
mCurrentBound = null;
}
}
private void parseOsmElement(final String name, final Attributes atts) throws OsmParseException {
try {
long osmId = Long.parseLong(atts.getValue("id"));
long osmVersion = Long.parseLong(atts.getValue("version"));
if (isNode(name)) {
int lon = (int) (Double.parseDouble(atts.getValue("lon")) * 1E7);
int lat = (int) (Double.parseDouble(atts.getValue("lat")) * 1E7);
mCurrentNode = mDb.createNode(osmId, osmVersion/*, status*/, lon, lat);
//Log.d(TAG, "Node: id="+osmId+" pos="+lon+","+lat);
} else if (isWay(name)) {
mCurrentWay = mDb.createWay(osmId, osmVersion/*, status*/);
//Log.d(TAG, "Way: id="+osmId);
} else if (isRelation(name)) {
mCurrentRel = mDb.createRelation(osmId, osmVersion/*, status*/);
//Log.d(TAG, "Relation: id="+osmId);
}
} catch (NumberFormatException e) {
throw new OsmParseException("Element unparsable");
}
}
private void parseBounds(final Attributes atts) throws OsmParseException {
try {
int minlat = (int) (Double.parseDouble(atts.getValue("minlat")) * 1E7);
int maxlat = (int) (Double.parseDouble(atts.getValue("maxlat")) * 1E7);
int minlon = (int) (Double.parseDouble(atts.getValue("minlon")) * 1E7);
int maxlon = (int) (Double.parseDouble(atts.getValue("maxlon")) * 1E7);
try {
mCurrentBound = mDb.createBounds(minlon, minlat, maxlon, maxlat);
} catch (OsmException e) {
throw new OsmParseException("Bounds are not correct");
}
} catch (NumberFormatException e) {
throw new OsmParseException("Bounds unparsable");
}
}
private void parseTag(final Attributes atts) {
OsmElement currentOsmElement = getCurrentOsmElement();
if (currentOsmElement == null) {
Log.e(TAG, "Parsing Error: no mCurrentOsmElement set!");
} else {
String k = atts.getValue("k");
String v = atts.getValue("v");
currentOsmElement.addOrUpdateTag(k, v);
//Log.d(TAG, "Tag, "+k+"="+v);
}
}
private void parseWayNode(final Attributes atts) throws OsmParseException {
try {
if (mCurrentWay == null) {
Log.e(TAG, "No mCurrentWay set!");
} else {
long nodeOsmId = Long.parseLong(atts.getValue("ref"));
OsmNode node = mDb.getNode(nodeOsmId);
mCurrentWay.addNode(node);
}
} catch (NumberFormatException e) {
throw new OsmParseException("WayNode unparsable");
}
}
private void parseRelationElement(final Attributes atts) throws OsmParseException {
try {
if (mCurrentRel == null) {
Log.e(TAG, "No mCurrentRel set!");
} else {
String type = atts.getValue("type");
String role = atts.getValue("role");
long ref = Long.parseLong(atts.getValue("ref"));
mCurrentRel.addMember(type, role, ref, mDb.getElement(ref));
//Log.d(TAG, "Rel, type="+type+" role="+role+" ref="+ref);
}
} catch (NumberFormatException e) {
throw new OsmParseException("RelationElement unparsable");
}
}
private OsmElement getCurrentOsmElement() {
if (mCurrentNode != null) {
return mCurrentNode;
}
if (mCurrentWay != null) {
return mCurrentWay;
}
if (mCurrentRel != null) {
return mCurrentRel;
}
return null;
}
private static boolean isNode(final String name) { return name.equalsIgnoreCase("node"); }
private static boolean isWay(final String name) { return name.equalsIgnoreCase("way"); }
private static boolean isWayNode(final String name) { return name.equalsIgnoreCase("nd"); }
private static boolean isBounds(final String name) { return name.equalsIgnoreCase("bounds"); }
private static boolean isRelation(final String name) { return name.equalsIgnoreCase("relation"); }
private static boolean isRelationMember(final String name) { return name.equalsIgnoreCase("member"); }
private static boolean isTag(final String name) { return name.equalsIgnoreCase("tag"); }
private static boolean isOsmElement(final String name) { return isNode(name) || isWay(name) || isRelation(name); }
}

View file

@ -0,0 +1,143 @@
package dk.network42.osmfocus;
import java.util.ArrayList;
import android.graphics.Canvas;
import android.graphics.Path;
import android.util.Log;
public class OsmRelation extends OsmElement {
private static final String TAG = "OsmRelation";
private class RelMember {
String mRole;
String mType;
long mRef;
OsmElement mElem;
boolean mRender;
}
protected final ArrayList<RelMember> mElems;
protected Path mPath = new Path();
boolean mDrawInners = false; // Set if inners have tags different from relation/outer
OsmElement mVisualDefiningElem; // Normally relation, but may be outer way
OsmRelation(final long osmId, final long osmVersion) {
super(osmId, osmVersion);
mElems = new ArrayList<RelMember>();
}
boolean hasRole(String role) {
for (RelMember member: mElems) {
if (member.mRole.equalsIgnoreCase(role)) {
return true;
}
}
return false;
}
long getRoleRef(String role) {
for (RelMember member: mElems) {
if (member.mRole.equalsIgnoreCase(role)) {
if (member.mElem == null) {
Log.d(TAG, "Relation w NULL elem, ref="+member.mRef);
return 0; // FIXME
}
return member.mElem.getOsmId();
}
}
return 0; // FIXME
}
void addMember(final String type, final String role, long ref, OsmElement e) {
RelMember elem = new RelMember();
elem.mRole = role;
elem.mType = type;
elem.mElem = e;
elem.mRef = ref;
elem.mRender = false;
mElems.add(elem);
}
/* Detect which object define how the relation should be rendered.
* In principle tags should be on relations, but sometimes they are
* on the outer element(s) */
void updateVisualDefiningElem() {
if (!hasTag("type", "multipolygon")) {
Log.d(TAG, "Relation "+mOsmId+" is not a multipolygon");
return;
}
mVisualDefiningElem = this;
for (RelMember member: mElems) {
if (member.mRole.equalsIgnoreCase("outer") &&
member.mElem != null &&
member.mElem.getTagCnt() > 0 &&
getTagCnt() == 1) {
// If relation only has one tag ('type') and we have an outer
// with tags, use the outer for defining the visuals
mVisualDefiningElem = member.mElem;
}
}
for (RelMember member: mElems) {
// If we have an inner that differs from the element defining
// the visuals of the relation, then we render it
if (member.mRole.equalsIgnoreCase("inner") &&
member.mElem != null &&
member.mElem.getTagCnt()>0 &&
!mVisualDefiningElem.tagsIdentical(member.mElem)) {
mDrawInners = true;
member.mRender = true;
}
}
}
void updateVisuals(OsmDB db) {
if (!hasTag("type", "multipolygon")) {
Log.d(TAG, "Relation "+mOsmId+" is not a multipolygon");
return;
}
super.updateVisuals(db);
mPath.rewind();
mPath.setFillType(Path.FillType.EVEN_ODD);
for (RelMember member: mElems) {
OsmWay way = (OsmWay) member.mElem;
if (way != null) {
if (this.mOsmId==446026) { Log.d(TAG, "Add Path "+way.mOsmId); }
mPath.addPath(way.mPath); // FIXME: Polygons may be build from several ways
} else {
Log.d(TAG, "Relation w id="+mOsmId+" is missing way with id="+member.mRef+" for role="+member.mRole);
}
}
updateVisualDefiningElem();
}
public void draw(Canvas canvas, OsmDB db, PaintConfig pcfg) {
//Log.d(TAG, "draw Relation: id="+mOsmId);
if (!pcfg.wireframe) {
pcfg.getWayPaints(this, mVisualDefiningElem);
if (pcfg.look != null) {
canvas.drawPath(mPath, pcfg.look);
}
if (pcfg.look2 != null) {
canvas.drawPath(mPath, pcfg.look2);
}
// if (pcfg.nodelook != null) {
// for (int ii = 0, size = mNodes.size(); ii<size; ii++)
// canvas.drawPoint(mNodes.get(ii).getX(db), mNodes.get(ii).getY(db), pcfg.nodelook);
// }
// if (mDrawInners) {
// for (RelMember member: mElems) {
// if (member.mRender) {
// member.mElem.draw(canvas, db, pcfg);
// }
// }
// }
} else { // Wireframe
if (pcfg.getLayer()==1) {
canvas.drawPath(mPath, pcfg.paint);
}
}
}
// FIXME
public double distTo(double lon, double lat_merc) { return Double.POSITIVE_INFINITY; }
public double angleTo(double lon, double lat_merc) { return 0; }
}

View file

@ -0,0 +1,124 @@
package dk.network42.osmfocus;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.zip.GZIPInputStream;
import android.util.Log;
// See http://wiki.openstreetmap.org/wiki/API_v0.6
public class OsmServer {
private static final String TAG = "OsmServer";
static private final String API_VERSION = "0.6";
static private final int SERVER_CONNECT_TIMEOUT_MS = 30*1000;
static private final int API_TIMEOUT_MS = 10*1000;
static final int API_MAX_DOWNLOAD_DEGREES = (int) 1E7/4;
static private final String DEFAULT_API_URL = "http://api.openstreetmap.org/api/"+API_VERSION+"/";
private final String mApiUrl;
private final String mAgent;
// See also http://wiki.openstreetmap.org/index.php/Getting_Data#Construct_an_URL_for_the_HTTP_API
// "The server may reject your region if it is larger than 1/4 degree in either dimension."
public OsmServer(String apiUrl, String agent) {
assert (agent!=null && agent.isEmpty());
mAgent = agent;
if (apiUrl==null || apiUrl.isEmpty()) {
mApiUrl = DEFAULT_API_URL;
} else {
mApiUrl = apiUrl;
}
}
// GET /api/0.6/map?bbox=left,bottom,right,top
// where:
// left is the longitude of the left (westernmost) side of the bounding box.
// bottom is the latitude of the bottom (southernmost) side of the bounding box.
// right is the longitude of the right (easternmost) side of the bounding box.
// top is the latitude of the top (northernmost) side of the bounding box.
//
// Example:
// http://api.openstreetmap.org/api/0.6/map?bbox=11.54,48.14,11.543,48.145
//
// Error codes
// HTTP status code 400 (Bad Request)
// When any of the node/way/relation limits are crossed
// HTTP status code 509 (Bandwidth Limit Exceeded)
// "Error: You have downloaded too much data. Please try again later."
//
static public InputStream getStreamForArea(String apiUrl, String agent, final GeoBBox area) throws OsmServerException, IOException {
assert (agent!=null && agent.isEmpty());
if (apiUrl==null || apiUrl.isEmpty()) {
apiUrl = DEFAULT_API_URL;
}
URL url = new URL(apiUrl+"map?bbox="+getApiString(area));
Log.d(TAG, "URL='"+url+"'");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(SERVER_CONNECT_TIMEOUT_MS);
con.setReadTimeout(API_TIMEOUT_MS);
con.setRequestProperty("Accept-Encoding", "gzip"); // Default, but set it explicitly anyway
con.setRequestProperty("User-Agent", agent);
if (con.getResponseCode() == -1) {
Log.w(TAG, "No response code for '"+url+"'");
throw new OsmServerException(-1, "No response code for '"+url+"'");
// TODO: Retry?
}
if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
Log.w(TAG, "Request NACK for '"+url+"'");
throw new OsmServerException(-1, "Request NACK for '"+url+
"', code "+con.getResponseCode()+
", message '"+con.getResponseMessage()+"'");
}
if ("gzip".equals(con.getHeaderField("Content-encoding"))) {
return new GZIPInputStream(new BufferedInputStream(con.getInputStream()));
} else {
return new BufferedInputStream(con.getInputStream());
}
}
public InputStream getStreamForArea(final GeoBBox area) throws OsmServerException, IOException {
return getStreamForArea(mApiUrl, mAgent, area);
}
// See also http://wiki.openstreetmap.org/index.php/Getting_Data#Construct_an_URL_for_the_HTTP_API
// "The server may reject your region if it is larger than 1/4 degree in either dimension."
static public boolean isValidSizeForApi(GeoBBox box) {
int wx = box.right-box.left;
int wy = box.top-box.bottom;
return (wx<=API_MAX_DOWNLOAD_DEGREES/2 && wy <= API_MAX_DOWNLOAD_DEGREES/2);
}
public static void makeBboxSizeValidForApi(GeoBBox box) {
if (! isValidSizeForApi(box)) {
int cx = box.right/2+box.left/2;
int cy = box.top/2+box.bottom/2;
box.left = cx-API_MAX_DOWNLOAD_DEGREES/2;
box.bottom = cy-API_MAX_DOWNLOAD_DEGREES/2;
box.right = cx+API_MAX_DOWNLOAD_DEGREES/2;
box.top = cy+API_MAX_DOWNLOAD_DEGREES/2;
box.check();
}
}
// Get BBox centered at point with given size. API maximums are enforced and box shrunk accordingly
// Size ('meters') measured to both sides of central point, i.e. box will be 2*meters wide
public static GeoBBox getBoxForPoint(final double lat, final double lon, final double meters) {
GeoBBox box = GeoBBox.getBoxForPoint(lat, lon, meters);
makeBboxSizeValidForApi(box);
return box;
}
// See also http://wiki.openstreetmap.org/index.php/Getting_Data#Construct_an_URL_for_the_HTTP_API
// Example: http://api.openstreetmap.org/api/0.6/map?bbox=11.54,48.14,11.543,48.145
public static String getApiString(GeoBBox box) {
return ""+box.left/1E7+","+box.bottom/1E7+","+box.right/1E7+","+box.top/1E7;
}
}

View file

@ -0,0 +1,14 @@
package dk.network42.osmfocus;
//import org.apache.http.HttpStatus;
public class OsmServerException extends OsmException {
// See e.g. http://wiki.openstreetmap.org/wiki/API_v0.6
private final int mHttpErr;
OsmServerException(final int httpErr, final String info) {
super(info);
mHttpErr = httpErr;
}
}

View file

@ -0,0 +1,268 @@
package dk.network42.osmfocus;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Locale;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.DisplayMetrics;
import android.util.Log;
public class OsmTile {
private static final String TAG = "OsmTile";
public static final int MIN_ZOOM = 0;
public static final int MAX_ZOOM = 18; // Standard OSM and mapnik
public static final int ZOOM_LEVELS = MAX_ZOOM-MIN_ZOOM;
// See http://wiki.openstreetmap.org/wiki/Tile_usage_policy
public static final int MAX_DOWNLOAD_THREADS = 2;
public int mX, mY, mZoom;
int mDownloadErrs = 0;
boolean mDownloadOngoing = false;
// FIXME: This should not be part of OsmTile, but rather an inherited class
private Bitmap mBitmap = null;
// Geo coords
protected GeoBBox mBox;
protected RectF mBox2 = new RectF(); // FIXME: Merge bboxes
// Screen coords (scaled and Mercator latitudes)
protected RectF mViewBox = new RectF();
public OsmTile(int x, int y, int zoom) {
mX = x;
mY = y;
mZoom = zoom;
mBox = tile2BBox(x, y, zoom);
mBox2.left = (float)(((float)mBox.left)/1E7);
mBox2.right = (float)(((float)mBox.right)/1E7);
mBox2.top = (float)(((float)mBox.bottom)/1E7);
mBox2.bottom = (float)(((float)mBox.top)/1E7);
mViewBox.left = (float) MapView.prescale * mBox2.left;
mViewBox.right = (float) MapView.prescale * mBox2.right;
mViewBox.top = (float)(MapView.prescale * GeoMath.latToMercator(mBox2.top));
mViewBox.bottom = (float)(MapView.prescale * GeoMath.latToMercator(mBox2.bottom));
}
public OsmTile(double lon, double lat, int zoom) {
this(lon2tile(lon, zoom), lat2tile(lat, zoom), zoom);
}
public Bitmap getBitmap() {
return mBitmap;
}
public int getByteSize() {
// FIXME: Wrong for vector tiles
return 4*256*256; // Fixed tile size for OSM in ARGB_8888
// Log.d(TAG, "Get size of tile "+this);
// Bitmap bm = getBitmap();
// if (bm==null) {
// Log.d(TAG, " = 0");
// return 0;
// }
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// return getBitmap().getAllocationByteCount();
// } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
// return bm.getRowBytes() * bm.getHeight();
// }
}
public boolean canDraw(Canvas canvas, PaintConfig pcfg) {
if (mBitmap != null) {
return true;
}
return false;
}
public boolean draw(Canvas canvas, PaintConfig pcfg) {
if (mBitmap != null) {
//Log.d(TAG, "Draw bitmap at "+mBox2+" ("+mViewBox+") zoom="+mZoom);
canvas.drawBitmap(mBitmap, null, mViewBox, pcfg.paint);
return true;
} else if (mDownloadErrs>0) {
drawError(canvas, pcfg);
}
return false;
}
public void drawX(Canvas canvas, PaintConfig pcfg) {
canvas.drawLine(mViewBox.left, mViewBox.bottom, mViewBox.right, mViewBox.top, pcfg.paint);
canvas.drawLine(mViewBox.left, mViewBox.top, mViewBox.right, mViewBox.bottom, pcfg.paint);
}
public void drawP(Canvas canvas, PaintConfig pcfg) {
float xc = (mViewBox.left+mViewBox.right)/2;
float yc = (mViewBox.bottom+mViewBox.top)/2;
canvas.drawLine(mViewBox.left, yc, mViewBox.right, yc, pcfg.paint);
canvas.drawLine(xc, mViewBox.top, xc, mViewBox.bottom, pcfg.paint);
}
public void drawFade(Canvas canvas, PaintConfig pcfg) {
canvas.drawRect(mViewBox, pcfg.fade);
}
public void drawError(Canvas canvas, PaintConfig pcfg) {
canvas.drawRect(mViewBox, pcfg.tileerr);
}
// public void drawText(Canvas canvas, Paint paint, String st) {
// float w = paint.measureText(st);
// canvas.drawText(st, mViewBox.centerX(), mViewBox.centerY(), paint);
// }
public void setQueuedForDownload() {
assert(mDownloadOngoing==false);
mDownloadOngoing = true;
}
public void clearQueuedForDownload() {
assert(mDownloadOngoing==true);
mDownloadOngoing = false;
}
public boolean isQueuedForDownload() {
return mDownloadOngoing;
}
public boolean download(String useragent, String provider) {
Bitmap bm = dlFromUrl(tileUrl(provider));
if (bm != null) {
mBitmap = flipBitmap(bm);
//Log.d(TAG, "Download done");
return true;
} else {
Log.e(TAG, "Download failed, tile="+this);
return false;
}
}
private Bitmap dlFromUrl(String tileurl) {
try {
//Log.d(TAG, "Loading tile "+tileurl);
URL url = new URL(tileurl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);
//Log.d(TAG, "Connect for tile download");
conn.connect();
InputStream input = conn.getInputStream();
//Log.d(TAG, "Decode bitmap");
Bitmap bm = BitmapFactory.decodeStream(input);
//Log.d(TAG, "Downloaded tile "+tileurl+", bbox="+mBox.toString());
return bm;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
private Bitmap flipBitmap(Bitmap bm) {
Matrix m = new Matrix();
m.preScale(1, -1);
Bitmap bmf = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), m, false);
if (bmf!=null)
bmf.setDensity(DisplayMetrics.DENSITY_DEFAULT);
return bmf;
}
static public int lon2tile(double lon, int zoom) {
int xtile = (int)Math.floor((lon + 180.0)/360.0 * (1<<zoom));
if (xtile < 0)
xtile = 0;
else if (xtile >= (1<<zoom))
xtile = (1<<zoom)-1;
return xtile;
}
static public int lat2tile(double lat, int zoom) {
int ytile = (int)Math.floor((1.0 - Math.log(Math.tan(Math.toRadians(lat)) + 1.0/Math.cos(Math.toRadians(lat))) / Math.PI) / 2 * (1<<zoom));
if (ytile < 0)
ytile = 0;
else if (ytile >= (1<<zoom))
ytile = (1<<zoom)-1;
return ytile;
}
static public double tile2lon(int x, int zoom) {
return x/Math.pow(2.0, zoom)*360.0 - 180.0;
}
static public double tile2lat(int y, int zoom) {
double n = Math.PI - (2.0*Math.PI * y)/Math.pow(2.0, zoom);
return Math.toDegrees(Math.atan(Math.sinh(n)));
}
// Convert world viewport coords to tile 'indexes'
static public Rect worldport2tileport(final RectF worldport, int zoom) {
Rect r = new Rect();
r.left = lon2tile(worldport.left, zoom);
r.right = lon2tile(worldport.right, zoom);
r.top = lat2tile(worldport.top, zoom);
r.bottom = lat2tile(worldport.bottom, zoom);
return r;
}
// See http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
static public String xy2Url(String provider, int x, int y, int zoom) {
String name;
name = String.format(Locale.US, "%s/%d/%d/%d.png", provider, zoom, x, y);
return name;
}
public String tileId(String provider) {
String name;
name = String.format(Locale.US, "%s/%d/%d/%d", provider, mZoom, mX, mY);
return name;
}
static public String tileId(String provider, int x, int y, int zoom) {
String name;
name = String.format(Locale.US, "%s/%d/%d/%d", provider, zoom, x, y);
return name;
}
static public String tileId(String provider, double lon, double lat, int zoom) {
String name;
name = String.format(Locale.US, "%s/%d/%d/%d", provider, zoom, lon2tile(lon, zoom), lat2tile(lat, zoom));
return name;
}
public String tileUrl(String provider) {
return xy2Url(provider, mX, mY, mZoom);
}
static public String tileUrl(String provider, double lon, double lat, int zoom) {
return xy2Url(provider, lon2tile(lon, zoom), lat2tile(lat, zoom), zoom);
}
static public String id2Url(String provider, String id) {
return String.format("%s/%s.png", provider, id);
}
static public GeoBBox pos2BBox(double lon, double lat, int zoom) {
return tile2BBox(lon2tile(lon, zoom), lat2tile(lat, zoom), zoom);
}
static public GeoBBox tile2BBox(int tilex, int tiley, int zoom) {
int left = (int)(tile2lon(tilex, zoom)* 1E7);
int right = (int)(tile2lon(tilex+1, zoom)* 1E7);
int top = (int)(tile2lat(tiley, zoom)* 1E7);
int bottom = (int)(tile2lat(tiley+1, zoom)* 1E7);
GeoBBox box = new GeoBBox(left, bottom, right, top);
return box;
}
public String toString() {
return String.format(Locale.US, "%d/%d/%d", mZoom, mX, mY);
}
}

View file

@ -0,0 +1,196 @@
package dk.network42.osmfocus;
import android.content.ComponentCallbacks2;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v4.util.LruCache;
import android.util.Log;
import android.view.View;
public abstract class OsmTileLayer extends MapLayer {
private static final String TAG = "OsmTileLayer";
TileLruCache mTiles;
OsmTileProvider mProvider;
//View mView;
Handler mHandler;
Handler mMainHandler=null;
String mAttrib=null;
int mLastZoom = -1;
public OsmTileLayer(OsmTileProvider provider, int maxCacheSize) {
mProvider = provider;
mTiles = new TileLruCache(maxCacheSize);
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message inputMessage) {
//OsmTile t = (OsmTile) inputMessage.obj;
switch (inputMessage.what) {
case OsmTileProvider.TASK_DOWNLOAD_START:
case OsmTileProvider.TASK_DOWNLOAD_COMPLETE:
//Log.d(TAG, "Got download complete, tile"+t+" tile cache size="+mTiles.size());
if (mMainHandler != null)
mMainHandler.obtainMessage(MainActivity.INVALIDATE_VIEW, this).sendToTarget();
break;
}
}
};
}
static String urlFromType(int type) {
if (type==MapLayer.MAPTYPE_OSM) {
return "http://a.tile.openstreetmap.org";
} else if (type==MapLayer.MAPTYPE_OSMCYCLEMAP) {
return "http://a.tile.opencyclemap.org/cycle";
}
return "";
}
public void setMainHandler(Handler handler) {
mMainHandler = handler;
}
public void draw(Canvas canvas, RectF worldport, PaintConfig pcfg) {
for (int ii=0; ii<getNumLayers(); ii++) { // Sub-layers
drawTiles(canvas, worldport, pcfg, ii);
}
}
public void drawTiles(Canvas canvas, RectF worldport, PaintConfig pcfg, int layer) {
int zoom = getZoom(pcfg);
Rect vrect = OsmTile.worldport2tileport(worldport, zoom);
//Log.d(TAG, "TilePort="+vrect);
if (zoom != mLastZoom) {
mProvider.clearPending();
mLastZoom = zoom;
}
// RectF's are reversed top-bottom wise
int circles = (Math.max(vrect.right-vrect.left, vrect.bottom-vrect.top)+1)/2;
int cx = (vrect.right+vrect.left+1)/2;
int cy = (vrect.bottom+vrect.top+1)/2;
//Log.d(TAG, "Circles: "+circles+" around ("+cx+","+cy+")");
drawTile(canvas, pcfg, cx, cy, zoom, layer);
if (circles==0)
return;
int xt, yt;
for (int cc=1; cc<=circles; cc++) {
//Log.d(TAG, "cc="+cc);
for (xt=cx-cc; xt<=cx+cc; xt++) {
yt = cy-cc;
//Log.d(TAG, "1: "+xt+","+yt);
if (xt>=vrect.left && xt <= vrect.right && yt >= vrect.top && yt <= vrect.bottom)
drawTile(canvas, pcfg, xt, yt, zoom, layer);
}
for (yt=cy-(cc-1); yt<=cy+cc-1; yt++) {
xt = cx+cc;
//Log.d(TAG, "2: "+xt+","+yt);
if (xt>=vrect.left && xt <= vrect.right && yt >= vrect.top && yt <= vrect.bottom)
drawTile(canvas, pcfg, xt, yt, zoom, layer);
}
for (xt=cx+cc; xt>=cx-cc; xt--) {
yt = cy+cc;
//Log.d(TAG, "3: "+xt+","+yt);
if (xt>=vrect.left && xt <= vrect.right && yt >= vrect.top && yt <= vrect.bottom)
drawTile(canvas, pcfg, xt, yt, zoom, layer);
}
for (yt=cy+cc-1; yt>=cy-(cc-1); yt--) {
xt = cx-cc;
//Log.d(TAG, "4: "+xt+","+yt);
if (xt>=vrect.left && xt <= vrect.right && yt >= vrect.top && yt <= vrect.bottom)
drawTile(canvas, pcfg, xt, yt, zoom, layer);
}
}
//Log.d(TAG, "Tile layer draw finished ");
}
public void drawLegends(Canvas canvas, RectF viewrect, PaintConfig pcfg) {
if (mAttrib != null) {
canvas.drawText(mAttrib, viewrect.centerX()-pcfg.attrib.measureText(mAttrib)/2,
viewrect.centerY()+viewrect.height()*0.4f, pcfg.attrib);
}
}
protected abstract String getTileId(int xt, int yt, int zoom);
protected abstract OsmTile createTile(int xt, int yt, int zoom);
protected abstract void handleMissingTile(OsmTile t, int layer);
protected void drawTile(Canvas canvas, PaintConfig pcfg, int xt, int yt, int zoom, int layer) {
//Log.d(TAG, "Draw tile: "+xt+","+yt+" zoom="+zoom);
String tid = getTileId(xt, yt, zoom);
OsmTile t = mTiles.get(tid);
if (t == null) {
t = createTile(xt, yt, zoom);
mTiles.put(tid, t);
//Log.d(TAG, "New tile, TID="+tid+" LruCache size="+mTiles.size());
handleMissingTile(t, layer);
t.drawX(canvas, pcfg);
} else {
if (! t.draw(canvas, pcfg)) {
t.drawX(canvas, pcfg);
}
}
}
// private void drawTileCoords(Canvas canvas, PaintConfig pcfg, OsmTile t) {
// String st = "";
// st.format("%d,%d,%d", t.mX, t.mY, t.mZoom);
// t.drawText(canvas, pcfg.paint, st);
// }
protected abstract void downloadTile(OsmTile t);
protected int getZoom(PaintConfig pcfg) {
return view2OsmZoom(pcfg.mScale);
}
// Convert from internal view scale value to OSM zoom levels
protected int view2OsmZoom(float scale) {
int zoom = (int) (16+(1.0+Math.log((double)scale)/Math.log(2.0)));
if (zoom > OsmTile.MAX_ZOOM)
zoom = OsmTile.MAX_ZOOM;
//Log.d(TAG, "Scale="+scale+" OsmZoom="+zoom);
return zoom;
}
public void onTrimMemory(int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
mTiles.trimToSize(mTiles.maxSize()/2);
} else if (level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
mTiles.trimToSize(0);
}
}
public int getMemorySize() {
return mTiles.size();
}
public void flushCache() {
mTiles.evictAll();
}
protected int getNumLayers() {
return 1;
}
// Attribute, like OSM data attribution
public void setAttrib(String attrib) {
mAttrib = attrib;
}
protected class TileLruCache extends LruCache<String, OsmTile> {
public TileLruCache(int maxSize) {
super(maxSize);
}
protected int sizeOf(String k, OsmTile v) {
return v.getByteSize();
}
};
}

View file

@ -0,0 +1,35 @@
package dk.network42.osmfocus;
import android.view.View;
public class OsmTileLayerBm extends OsmTileLayer {
private static final String TAG = "OsmTileLayerBm";
String mProviderArg = "";
public OsmTileLayerBm(OsmTileProvider provider, int maxCacheSize) {
super(provider, maxCacheSize);
//mProviderArg = "http://a.tile.openstreetmap.org";
//mProviderArg = "http://a.tile.opencyclemap.org/cycle";
}
public void setProviderUrl(String url) {
mProviderArg = url;
flushCache();
}
protected OsmTile createTile(int xt, int yt, int zoom) {
return new OsmTile(xt, yt, zoom);
}
protected void handleMissingTile(OsmTile t, int layer) {
downloadTile(t);
}
protected void downloadTile(OsmTile t) {
mProvider.downloadTile(mProviderArg, t, mHandler);
}
protected String getTileId(int xt, int yt, int zoom) {
return OsmTile.tileId(mProviderArg, xt, yt, zoom);
}
}

View file

@ -0,0 +1,221 @@
package dk.network42.osmfocus;
import java.util.ArrayList;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.Log;
import android.view.View;
public class OsmTileLayerVector extends OsmTileLayer {
private static final String TAG = "OsmTileLayerVector";
private static final int TILEZOOM = 16; // Vector tile zoom level
private static final int MAX_POI = 8;
private static final float MIN_SCALE = 0.5f;
String mProviderArg;
SharedData mG;
ArrayList<OsmElement> mNearest = new ArrayList<OsmElement>(MAX_POI);
RectF [] mUsedArea = new RectF[MAX_POI];
boolean filterWayNodes = false;
public OsmTileLayerVector(OsmTileProvider provider, int maxCacheSize) {
super(provider, maxCacheSize);
mProviderArg = "";
}
public void setSharedData(SharedData data) {
mG = data;
}
protected void tryAutoDownload() {
if (mG.mVectorAutoDownload == SharedData.AUTODOWNLOAD_MANUAL)
return;
int xt = OsmTile.lon2tile(mG.mLon, TILEZOOM);
int yt = OsmTile.lat2tile(mG.mLat, TILEZOOM);
int x, y, c=0;
String tid;
if (mG.mVectorAutoDownload == SharedData.AUTODOWNLOAD_AUTOMATIC2)
c=1;
for (x=xt-c; x<=xt+c; x++) {
for (y=yt-c; y<=yt+c; y++) {
tid = OsmTile.tileId(mProviderArg, x, y, TILEZOOM);
OsmTileVector tile = (OsmTileVector) mTiles.get(tid);
if (tile != null && tile.mDb == null && !tile.isQueuedForDownload()) {
downloadTile(tile);
}
}
}
}
protected OsmTile createTile(int xt, int yt, int zoom) {
return new OsmTileVector(xt, yt, zoom);
}
// Called once immediately after createTile()
protected void handleMissingTile(OsmTile t, int layer) {
tryAutoDownload();
}
// Shading are only drawn on layer 1
protected void drawTile(Canvas canvas, PaintConfig pcfg, int xt, int yt, int zoom, int layer) {
//Log.d(TAG, "Draw tile: "+xt+","+yt+" zoom="+zoom);
String tid = getTileId(xt, yt, zoom);
OsmTile t = mTiles.get(tid);
if (t == null) {
t = createTile(xt, yt, zoom);
mTiles.put(tid, t);
//Log.d(TAG, "New tile, TID="+tid+" LruCache size="+mTiles.size());
handleMissingTile(t, layer);
if (layer==1)
t.drawX(canvas, pcfg);
} else {
if (layer==0) {
t.draw(canvas, pcfg);
}
if (layer==1 && !t.canDraw(canvas, pcfg)) {
t.drawX(canvas, pcfg);
}
}
}
protected void downloadTile(OsmTile t) {
if (t.mDownloadErrs < 2) {
mProvider.downloadTile(mProviderArg, t, mHandler);
} else {
//Log.d(TAG, "Skipping download, errs="+t.mDownloadErrs);
}
}
protected String getTileId(int xt, int yt, int zoom) {
return OsmTile.tileId(mProviderArg, xt, yt, zoom);
}
// See: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
@Override
protected int view2OsmZoom(float scale) {
return 16;
}
public void download(double lon, double lat) {
int xt = OsmTile.lon2tile(lon, TILEZOOM);
int yt = OsmTile.lat2tile(lat, TILEZOOM);
String tid = OsmTile.tileId(mProviderArg, xt, yt, TILEZOOM);
OsmTile t = mTiles.get(tid);
if (t == null) {
t = createTile(xt, yt, TILEZOOM);
}
downloadTile(t);
}
public int closestElements(double lon, double lat, int max, ArrayList<OsmElement> near) {
near.clear();
int xt = OsmTile.lon2tile(lon, TILEZOOM);
int yt = OsmTile.lat2tile(lat, TILEZOOM);
int x, y, c=1;
String tid;
for (x=xt-c; x<=xt+c; x++) {
for (y=yt-c; y<=yt+c; y++) {
tid = OsmTile.tileId(mProviderArg, x, y, TILEZOOM);
OsmTileVector t = (OsmTileVector) mTiles.get(tid);
if (t != null && t.mDb != null) {
t.mDb.closestElements(lon, lat, max, near);
}
}
}
return near.size();
}
public void draw(Canvas canvas, RectF worldport, PaintConfig pcfg) {
if (pcfg.mScale<MIN_SCALE)
return;
for (int ii=0; ii<getNumLayers(); ii++) { // Sub-layers
drawTiles(canvas, worldport, pcfg, ii);
}
tryAutoDownload();
if (closestElements(mG.mLon, mG.mLat, mG.mPoisToShow, mNearest)==0)
return;
OsmDB.quadSort(mNearest);
//OsmDB.radianSortCW(mNearest, mG.mLon, GeoMath.latToMercator(mG.mLat), 0); //Math.PI*3.0/4.0);
int pos=0;
for (int ii = 0; ii<mNearest.size(); ++ii) {
OsmElement e = mNearest.get(ii);
// if (!filterWayNodes || !((OsmNode)mNearest.get(ii)).mIsWayNode) {
//Log.d(TAG, "Hightlight "+e);
e.mDb.highlightElem(canvas, mG.mPcfg, e, ii);
pos++;
if (pos==mG.mPoisToShow) break;
// }
}
// FIXME: Needs scaling to screen coords
//canvas.drawCircle((float)mG.mLon, (float)GeoMath.latToMercator(mG.mLat),
// (float) mNearest.get(mNearest.size()-1).distTo(mG.mLon, mG.mLat), mG.mPcfg.horizon);
}
private Paint.Align[] xloc4 = {Paint.Align.LEFT, Paint.Align.RIGHT, Paint.Align.LEFT, Paint.Align.RIGHT};
private Paint.Align[] yloc4 = {Paint.Align.LEFT, Paint.Align.LEFT, Paint.Align.RIGHT, Paint.Align.RIGHT};
private Paint.Align[] xloc8 = {Paint.Align.LEFT, Paint.Align.CENTER, Paint.Align.RIGHT, Paint.Align.LEFT, Paint.Align.RIGHT, Paint.Align.LEFT, Paint.Align.CENTER, Paint.Align.RIGHT};
private Paint.Align[] yloc8 = {Paint.Align.LEFT, Paint.Align.LEFT, Paint.Align.LEFT, Paint.Align.CENTER, Paint.Align.CENTER, Paint.Align.RIGHT, Paint.Align.RIGHT, Paint.Align.RIGHT};
public void drawLegends(Canvas canvas, RectF viewrect, PaintConfig pcfg) {
if (pcfg.mScale<MIN_SCALE) {
String txt;
if (pcfg.mBackMapType == MAPTYPE_INTERNAL) {
txt = mG.mCtx.getString(R.string.info_notshowvectoratzoomlevel);
} else {
txt = mG.mCtx.getString(R.string.info_notshowpoiatzoomlevel);
}
canvas.drawText(txt, viewrect.centerX()-mG.mPcfg.debughud.measureText(txt)/2,
viewrect.centerY()-mG.mPcfg.mHUDspacing, mG.mPcfg.debughud);
return;
}
int pos=0;
Paint.Align[] xloc, yloc;
if (mG.mPoisToShow==8) {
xloc = xloc8; yloc = yloc8;
} else {
xloc = xloc4; yloc = yloc4;
}
for (int ii = 0; ii<mNearest.size(); ++ii) {
if (!filterWayNodes || !((OsmNode)mNearest.get(ii)).mIsWayNode) {
mUsedArea[ii] = mNearest.get(ii).drawTags(canvas, viewrect, mG.mShowPoiLines,
xloc[pos], yloc[pos], mG.mPcfg, ii);
pos++;
if (pos==mG.mPoisToShow) break;
}
}
String tid = OsmTile.tileId(mProviderArg, mG.mLon, mG.mLat, TILEZOOM);
OsmTileVector t = (OsmTileVector) mTiles.get(tid);
if (t == null || t.mDb == null) {
//Log.d(TAG, "No focus tile (id="+tid+") at "+mG.mLon+","+mG.mLat);
tryAutoDownload();
String txt;
if (t != null && t.mDownloadErrs > 0) {
txt = mG.mCtx.getString(R.string.info_errdownloadarea);
} else {
if (mG.mVectorAutoDownload==SharedData.AUTODOWNLOAD_MANUAL) {
txt = mG.mCtx.getString(R.string.info_locationoutside);
} else {
txt = mG.mCtx.getString(R.string.info_downloading);
}
}
canvas.drawText(txt, viewrect.centerX()-mG.mPcfg.debughud.measureText(txt)/2,
viewrect.centerY()-mG.mPcfg.mHUDspacing, mG.mPcfg.debughud);
}
}
protected int getNumLayers() {
return 2; // Vector and shaded
}
}

View file

@ -0,0 +1,119 @@
package dk.network42.osmfocus;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import android.os.Handler;
import android.util.Log;
public class OsmTileProvider {
static private final String TAG = "OsmTileProvider";
static final int TASK_DOWNLOAD_START = 1;
static final int TASK_DOWNLOAD_FAILED = 2;
static final int TASK_DOWNLOAD_COMPLETE = 3;
static final int MAX_THREADS = 4;
final int KEEP_ALIVE_TIME = 1;
final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
private BlockingQueue<Runnable> mWorkQueue;
private ThreadPoolExecutor mTrdPool;
private Queue<OsmTileDownloader> mTaskQueue;
private String mUserAgent;
OsmTileProvider(String agent) {
this(agent, Math.min(Runtime.getRuntime().availableProcessors(), MAX_THREADS));
}
OsmTileProvider(String agent, int maxTrds) {
mUserAgent = new String(agent);
final int trds = Math.min(maxTrds, Runtime.getRuntime().availableProcessors());
//Log.d(TAG, "Threads="+trds);
mWorkQueue = new LinkedBlockingQueue<Runnable>();
mTaskQueue = new LinkedBlockingQueue<OsmTileDownloader>();
mTrdPool = new ThreadPoolExecutor(trds, trds, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, mWorkQueue);
}
public int getActiveDownloads() {
return mTrdPool.getActiveCount();
}
public void clearPending() {
synchronized (this) {
//Log.d(TAG, "clearPending start size="+mWorkQueue.size());
OsmTileDownloader[] pendingArray = new OsmTileDownloader[mWorkQueue.size()];
mWorkQueue.toArray(pendingArray);
int len = pendingArray.length;
//Log.d(TAG, "ArrayLen="+len);
for (int ii = 0; ii < len; ii++) {
//Log.d(TAG, "dlTask="+pendingArray[ii]);
Thread trd = pendingArray[ii].mThread;
if (trd != null) {
trd.interrupt();
}
}
}
//Log.d(TAG, "clearPending done");
}
public void cancelDownload(OsmTile tile) {
synchronized (this) {
OsmTileDownloader[] pendingArray = new OsmTileDownloader[mWorkQueue.size()];
mWorkQueue.toArray(pendingArray);
int len = pendingArray.length;
for (int ii = 0; ii < len; ii++) {
if (pendingArray[ii].mTile == tile) {
Thread trd = pendingArray[ii].mThread;
if (trd != null) {
trd.interrupt();
}
}
}
}
}
public OsmTileDownloader downloadTile(final String providerArg, OsmTile tile, Handler h) {
tile.setQueuedForDownload();
OsmTileDownloader task = mTaskQueue.poll();
if (task == null) {
task = this.new OsmTileDownloader();
}
task.mProv = this;
task.mProviderArg = providerArg;
task.mTile = tile;
task.mHandler = h;
mTrdPool.execute(task);
return task;
}
protected class OsmTileDownloader implements Runnable {
OsmTileProvider mProv;
String mProviderArg;
OsmTile mTile;
Thread mThread;
Handler mHandler;
public void run() {
mThread = Thread.currentThread();
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
//Log.d(TAG, "Task started, tile="+mTile);
mHandler.obtainMessage(TASK_DOWNLOAD_START, mTile).sendToTarget();
if (!mTile.download(mUserAgent, mProviderArg)) {
mTile.mDownloadErrs++;
}
// Mark done before new task starts - see OsmTileProvider.cancelDownload()
synchronized (mProv) {
mThread = null;
}
mTile.clearQueuedForDownload();
//Log.d(TAG, "Task ended, tile="+mTile);
//Log.d(TAG, "Sending TASK_DOWNLOAD_COMPLETE");
mHandler.obtainMessage(TASK_DOWNLOAD_COMPLETE, mTile).sendToTarget();
}
}
}

View file

@ -0,0 +1,95 @@
package dk.network42.osmfocus;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import android.graphics.Canvas;
import android.util.Log;
public class OsmTileVector extends OsmTile {
private static final String TAG = "OsmTileVector";
OsmDB mDb = null;
public OsmTileVector(int x, int y, int zoom) {
super(x, y, zoom);
}
public boolean canDraw(Canvas canvas, PaintConfig pcfg) {
return (mDb != null);
}
public boolean draw(Canvas canvas, PaintConfig pcfg) {
if (mDb != null) {
//Log.d(TAG, "Draw vector tile at "+mBox2+" ("+mViewBox+") "+mDb);
if (pcfg.wireframe || pcfg.mBackMapType == MapLayer.MAPTYPE_INTERNAL) {
mDb.draw(canvas, mViewBox, pcfg);
}
return true;
} else if (mDownloadErrs>0) {
drawError(canvas, pcfg);
}
return false;
}
public void drawX(Canvas canvas, PaintConfig pcfg) {
drawFade(canvas, pcfg);
}
public boolean download(String useragent, String provider) {
//Log.d(TAG, "Download vector");
//Log.d(TAG, "Acquire Osm server stream, box="+mBox);
try {
InputStream istream = OsmServer.getStreamForArea(null, useragent, mBox);
OsmDB db = loadData(istream, mBox.toString());
if (db != null) {
db.updateAndVacuum();
mDb = db;
Log.d(TAG, "Read db tile: "+mBox+" TID="+this);
return true;
} else {
Log.e(TAG, "Reading of db tile failed "+mBox);
}
} catch (OsmServerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ParserConfigurationException pce) {
Log.e(TAG, "SAX parse error", pce);
} catch (SAXException se) {
Log.e(TAG, "SAX error", se);
}
return false;
}
protected OsmDB loadData(InputStream istream, String info) throws ParserConfigurationException, SAXException, IOException {
String start = java.text.DateFormat.getDateTimeInstance().format(Calendar.getInstance().getTime());
//Log.d(TAG, "Start download at "+start+", info="+info);
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
OsmParser parser = new OsmParser();
xr.setContentHandler(parser);
xr.parse(new InputSource(istream));
String end = java.text.DateFormat.getDateTimeInstance().format(Calendar.getInstance().getTime());
//Log.d(TAG, "Download finished at "+end+", info="+info);
OsmDB db = parser.getStorage();
return db;
// db.updateAndVacuum();
// db.merge(mG.mDb);
// String enddb = java.text.DateFormat.getDateTimeInstance().format(Calendar.getInstance().getTime());
// Log.d(TAG, "Database merged at "+enddb+", info="+info);
// mG.mDb = db;
// mapView.postInvalidate();
}
}

View file

@ -0,0 +1,138 @@
package dk.network42.osmfocus;
import java.util.ArrayList;
import java.util.List;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.Log;
public class OsmWay extends OsmElement {
private static final String TAG = "OsmWay";
protected final ArrayList<OsmNode> mNodes;
protected boolean mIsClosed = false;
protected Path mPath = new Path();
double mLonClose, mLatMercClose;
int mCloseStartNode;
boolean mIsMultipolyOuter = false; // Drawn as relation multipolygon instead if true
OsmWay(final long osmId, final long osmVersion) {
super(osmId, osmVersion);
mNodes = new ArrayList<OsmNode>();
}
void addNode(final OsmNode node) {
//Log.d(TAG, "id="+mOsmId+": ("+node.getLat()+","+node.getLon()+"), nodes="+mNodes.size());
//mPath.moveTo(node.getX(), node.getY());
mNodes.add(node);
}
public List getNodes() {
return mNodes;
}
// For screen mapping when db translation is not active
public float getX() { return ((float) mLonClose*MapView.prescale); }
public float getY() { return ((float) mLatMercClose*MapView.prescale); }
public double distTo(double lon, double lat_merc) {
int size = mNodes.size();
double dd, dst = Double.POSITIVE_INFINITY;
for (int ii=0; ii<size-1; ii++) {
dd = GeoMath.getLineDistance((float)lon, (float)lat_merc,
(float)mNodes.get(ii).getLon(), (float)mNodes.get(ii).getLatMerc(),
(float)mNodes.get(ii+1).getLon(), (float)mNodes.get(ii+1).getLatMerc());
if (dd<dst) {
dst = dd;
double[] pts = GeoMath.nearestPoint(lon, lat_merc,
mNodes.get(ii).getLon(), mNodes.get(ii).getLatMerc(),
mNodes.get(ii+1).getLon(), mNodes.get(ii+1).getLatMerc());
mLonClose = pts[0];
mLatMercClose = pts[1];
mCloseStartNode = ii;
// Log.d(TAG, "OSMid="+mOsmId+" lon="+lon+", latMerc="+lat_merc+
// " ii="+ii+
// " p1("+mNodes.get(ii).mOsmId+")=("+(float)mNodes.get(ii).getLon()+","+(float)mNodes.get(ii).getLatMerc()+")"+
// " p2("+mNodes.get(ii+1).mOsmId+")=("+(float)mNodes.get(ii+1).getLon()+","+(float)mNodes.get(ii+1).getLatMerc()+")"+
// ", ii="+ii+", dist="+dst);
}
}
//return Math.hypot(getLon()-lon, GeoMath.latToMercator(getLat())-GeoMath.latToMercator(lat));
return dst;
}
// Assumes distTo() has been called
public double angleTo(double lon, double lat_merc) {
return Math.atan2(mLatMercClose-lat_merc, mLonClose-lon);
}
void updateVisuals(OsmDB db)
{
super.updateVisuals(db);
updatePath(db);
}
private void updatePath(OsmDB db)
{
//Log.d(TAG, "updatePath() for way: name="+getTagWithKey("name")+" nodes="+mNodes.size());
int size = mNodes.size();
mPath.rewind();
mPath.moveTo(mNodes.get(0).getX(db), mNodes.get(0).getY(db));
for (int ii=1; ii<size; ii++) {
mPath.lineTo(mNodes.get(ii).getX(db), mNodes.get(ii).getY(db));
}
}
public void draw(Canvas canvas, OsmDB db, PaintConfig pcfg)
{
//Log.d(TAG, "draw Way: name="+getTagWithKey("name")+" nodes="+mNodes.size());
Paint look, look2;
Paint nodelook;
if (!pcfg.wireframe) {
if (mIsMultipolyOuter)
return;
pcfg.getWayPaints(this, null);
look = pcfg.look;
look2 = pcfg.look2;
nodelook = pcfg.nodelook;
if (mOsmId==52009742 || mOsmId==52009746 || mOsmId==52009741 || mOsmId==52009739) {
Log.d(TAG, "Id="+mOsmId+": Multio="+mIsMultipolyOuter+" look="+look+" look2="+look2+" nodelook="+nodelook);
}
if (look != null) {
canvas.drawPath(mPath, look);
}
if (look2 != null) {
canvas.drawPath(mPath, look2);
}
if (nodelook != null) {
for (int ii = 0, size = mNodes.size(); ii<size; ii++)
canvas.drawPoint(mNodes.get(ii).getX(db), mNodes.get(ii).getY(db), nodelook);
}
} else { // Wireframe
if (pcfg.getLayer()==0) {
canvas.drawPath(mPath, pcfg.paint);
// int size = mNodes.size();
// for (int ii=0; ii<size-1; ii++) {
// canvas.drawLine(mNodes.get(ii).getX(), mNodes.get(ii).getY(),
// mNodes.get(ii+1).getX(), mNodes.get(ii+1).getY(), pcfg.paint);
// }
for (int ii = 0, size = mNodes.size(); ii<size; ii++) {
//canvas.drawPoint(mNodes.get(ii).getX(db), mNodes.get(ii).getY(db), pcfg.paint);
canvas.drawCircle(mNodes.get(ii).getX(db), mNodes.get(ii).getY(db), 1.0f/pcfg.mScale, pcfg.paint);
}
}
}
}
public void highlight(Canvas canvas, OsmDB db, PaintConfig pcfg, int style)
{
// Log.d(TAG, "Highlight("+mOsmId+") mCloseStartNode="+mCloseStartNode+" = ("+
// mNodes.get(mCloseStartNode).getX(db)+","+mNodes.get(mCloseStartNode).getY(db)+")-("+
// mNodes.get(mCloseStartNode+1).getX(db)+","+mNodes.get(mCloseStartNode+1).getY(db)+")");
//canvas.drawLine(mNodes.get(mCloseStartNode).getX(db), mNodes.get(mCloseStartNode).getY(db),
// mNodes.get(mCloseStartNode+1).getX(db), mNodes.get(mCloseStartNode+1).getY(db), pcfg.focus[style]);
canvas.drawPath(mPath, pcfg.focus[style]);
}
}

View file

@ -0,0 +1,405 @@
package dk.network42.osmfocus;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.preference.PreferenceManager;
import android.util.Log;
public class PaintConfig {
private static final String TAG = "PaintConfig";
public static final float DEFAULT_ZOOM = 2.5f;
Paint debughud = new Paint();
Paint bounds = new Paint();
Paint paint = new Paint();
Paint waystroke = new Paint();
Paint waystroke2 = new Paint();
Paint wayfill2 = new Paint();
Paint service = new Paint();
Paint service2 = new Paint();
Paint waystrokesec = new Paint(); // secondary+primary roads
Paint waystrokesec2 = new Paint();
Paint waystroketert = new Paint(); // tertiary+unclassifed roads
Paint waystroketert2 = new Paint();
Paint waystroke_undef = new Paint();
Paint rail = new Paint(); // tertiary+unclassifed roads
Paint rail2 = new Paint();
Paint track = new Paint();
Paint path = new Paint();
Paint water = new Paint();
Paint buildings = new Paint();
Paint buildings2 = new Paint();
Paint nature = new Paint();
Paint gps = new Paint();
Paint gps2 = new Paint();
Paint tag = new Paint();
Paint tagback = new Paint();
Paint tagback2 = new Paint();
Paint tagback2stroke = new Paint();
Paint tagback3 = new Paint();
Paint tagframe = new Paint();
Paint horizon = new Paint();
Paint[] focus = new Paint[8];
Paint[] focus2 = new Paint[8];
Paint fade = new Paint();
Paint tileerr = new Paint();
Paint attrib = new Paint();
int mLayer;
Matrix viewmatrix = new Matrix();
Matrix inv = new Matrix();
float[] pts = new float[2]; // Scratchpad
float[] pts2 = new float[2]; // Scratchpad
boolean wireframe = false;
float tagtextsize = 18;
boolean antialias = true;
float mHUDspacing, mListSpacing;
float mScale = DEFAULT_ZOOM;
float densityScale;
//int mBackMapType = MAPTYPE_OSM;
int mBackMapType = MapLayer.MAPTYPE_INTERNAL;
public void setLayer(int layer) { mLayer = layer; }
public int getLayer() { return mLayer; }
public void setScale(float scale, Context ctx) {
mScale = scale;
update(ctx);
}
public float getScale() { return mScale; }
public void update(Context ctx) {
densityScale = ctx.getResources().getDisplayMetrics().density;
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(ctx);
//Log.d(TAG, "Pref:LabelSize=" + sharedPrefs.getString("pref_labelsize", "15"));
tagtextsize = densityScale * Float.parseFloat(sharedPrefs.getString("pref_labelsize", "15.0"));
//wireframe = sharedPrefs.getBoolean("pref_wireframe", false);
//Toast toast = Toast.makeText(ctx, "Textsize="+tagtextsize, Toast.LENGTH_SHORT);
//toast.show();
debughud.setColor(Color.BLACK);
debughud.setAntiAlias(antialias);
debughud.setStrokeWidth(0);
debughud.setTextSize(tagtextsize);
mHUDspacing = tagtextsize;
attrib.setColor(Color.GRAY);
attrib.setAntiAlias(antialias);
attrib.setStrokeWidth(0);
attrib.setTextSize(densityScale*10);
bounds.setColor(Color.BLACK);
bounds.setStrokeWidth(0);
bounds.setStyle(Paint.Style.STROKE);
paint.setColor(Color.BLACK);
paint.setAntiAlias(antialias);
paint.setStrokeWidth(0);
paint.setStyle(Paint.Style.STROKE);
paint.setTextSize(tagtextsize);
mListSpacing = paint.getTextSize()+1;
fade.setColor(Color.BLACK);
fade.setStrokeWidth(0);
fade.setAlpha(42); // Larger means less opaque
tileerr.setColor(Color.RED);
tileerr.setAlpha(42); // Larger means less opaque
waystroke.setColor(Color.rgb(190, 190, 190));
waystroke.setAntiAlias(antialias);
waystroke.setStrokeWidth(6);
waystroke.setStrokeJoin(Paint.Join.ROUND);
waystroke.setStrokeCap(Paint.Cap.ROUND);
waystroke.setStyle(Paint.Style.STROKE);
waystroke2.setColor(Color.rgb(254, 254, 254));
waystroke2.setAntiAlias(antialias);
waystroke2.setStrokeWidth(5);
waystroke2.setStrokeJoin(Paint.Join.ROUND);
waystroke2.setStrokeCap(Paint.Cap.ROUND);
waystroke2.setStyle(Paint.Style.STROKE);
wayfill2.set(waystroke2);
wayfill2.setStyle(Paint.Style.FILL_AND_STROKE);
service.set(waystroke);
service.setStrokeWidth(3);
service2.set(waystroke2);
service2.setStrokeWidth(2.4f);
waystrokesec.setColor(Color.rgb(163, 123, 72));
waystrokesec.setAntiAlias(antialias);
waystrokesec.setStrokeWidth(10);
waystrokesec.setStrokeJoin(Paint.Join.ROUND);
waystrokesec.setStrokeCap(Paint.Cap.ROUND);
waystrokesec.setStyle(Paint.Style.STROKE);
waystrokesec2.setColor(Color.rgb(237, 237, 201));
waystrokesec2.setAntiAlias(antialias);
waystrokesec2.setStrokeWidth(8);
waystrokesec2.setStrokeJoin(Paint.Join.ROUND);
waystrokesec2.setStrokeCap(Paint.Cap.ROUND);
waystrokesec2.setStyle(Paint.Style.STROKE);
waystroketert.setColor(Color.rgb(190, 190, 190));
waystroketert.setAntiAlias(antialias);
waystroketert.setStrokeWidth(10);
waystroketert.setStrokeJoin(Paint.Join.ROUND);
waystroketert.setStrokeCap(Paint.Cap.ROUND);
waystroketert.setStyle(Paint.Style.STROKE);
waystroketert2.setColor(Color.rgb(254, 254, 254));
waystroketert2.setAntiAlias(antialias);
waystroketert2.setStrokeWidth(8);
waystroketert2.setStrokeJoin(Paint.Join.ROUND);
waystroketert2.setStrokeCap(Paint.Cap.ROUND);
waystroketert2.setStyle(Paint.Style.STROKE);
waystroke_undef.setColor(Color.rgb(210,210,210));
waystroke_undef.setAntiAlias(antialias);
waystroke_undef.setStrokeWidth(0);
waystroke_undef.setStyle(Paint.Style.STROKE);
waystroke_undef.setTextSize(16);
//waypaint_undef.setPathEffect(new DashPathEffect(new float[] {1,2}, 0));
rail.setColor(Color.rgb(190, 190, 190));
rail.setAntiAlias(antialias);
rail.setStrokeWidth(2.5f);
rail.setStyle(Paint.Style.STROKE);
rail2.setColor(Color.rgb(254, 254, 254));
rail2.setAntiAlias(antialias);
rail2.setStrokeWidth(2);
rail2.setStyle(Paint.Style.STROKE);
rail2.setPathEffect(new DashPathEffect(new float[] {2f,2f}, 0));
path.setColor(Color.rgb(190, 108, 108)); // Paths and cycleways
path.setAntiAlias(antialias);
path.setStrokeWidth(1.5f);
path.setStyle(Paint.Style.STROKE);
path.setPathEffect(new DashPathEffect(new float[] {4f,1.5f}, 0));
track.set(path);
track.setColor(Color.rgb(156, 107, 8));
track.setStrokeWidth(2.5f);
buildings.setColor(Color.rgb(224, 224, 224)); // Fill
buildings.setAntiAlias(antialias);
buildings.setStrokeWidth(0);
buildings2.setColor(Color.rgb(145, 145, 145)); // Outline
buildings2.setAntiAlias(antialias);
buildings2.setStrokeWidth(0);
buildings2.setStyle(Paint.Style.STROKE);
nature.setColor(Color.rgb(212, 228, 196));
nature.setAntiAlias(antialias);
nature.setStrokeWidth(0);
water.setColor(Color.rgb(181, 208, 208));
water.setAntiAlias(antialias);
water.setStrokeWidth(0);
gps.setColor(Color.rgb(0, 0, 192)); // Circle defining physical location and accuracy
gps.setAntiAlias(antialias);
gps.setStrokeWidth(0);
gps.setAlpha(32);
gps2.setColor(Color.rgb(0, 0, 200));
gps2.setAntiAlias(antialias);
gps2.setStrokeWidth(0);
gps2.setStyle(Paint.Style.STROKE);
tag.setColor(Color.rgb(0, 0, 0));
tag.setAntiAlias(antialias);
tag.setStrokeWidth(0);
tag.setTextSize(tagtextsize);
tagback.setColor(Color.rgb(230, 230, 140));
tagback.setAntiAlias(antialias);
tagback.setStrokeWidth(0);
//tagback.setAlpha(190);
tagback2.setColor(Color.rgb(160, 240, 160));
tagback2.setAntiAlias(antialias);
tagback2.setStrokeWidth(0);
//tagback2stroke.setAlpha(190);
tagback2stroke.setColor(Color.rgb(160, 240, 160));
tagback2stroke.setAntiAlias(antialias);
tagback2stroke.setStrokeWidth(4);
//tagback2stroke.setAlpha(200);
tagback3.setColor(Color.WHITE);
tagback3.setAntiAlias(antialias);
tagback3.setStrokeWidth(0);
//tagback3.setAlpha(220);
tagframe.setColor(Color.BLACK);
tagframe.setAntiAlias(antialias);
tagframe.setStrokeWidth(1);
tagframe.setStyle(Paint.Style.STROKE);
horizon.setColor(Color.rgb(0, 0, 200));
horizon.setAntiAlias(antialias);
horizon.setStrokeWidth(0);
horizon.setStyle(Paint.Style.STROKE);
for (int ii=0; ii<8; ii++) { // highlighted objects
focus[ii] = new Paint();
switch (ii%8) {
// See http://developer.android.com/design/style/color.html
case 0:
focus[ii].setColor(Color.rgb(0xff, 0x44, 0x44));
break;
case 1:
focus[ii].setColor(Color.rgb(0xff, 0xbb, 0x33));
break;
case 2:
focus[ii].setColor(Color.rgb(0x99, 0xcc, 0));
break;
case 3:
focus[ii].setColor(Color.rgb(0xaa, 0x66, 0xcc));
break;
case 4:
focus[ii].setColor(Color.rgb(0x33, 0xb5, 0xe5));
break;
// Darker variants below
case 5:
focus[ii].setColor(Color.rgb(0xcc, 0, 0));
break;
case 6:
focus[ii].setColor(Color.rgb(0xff, 0x88, 0));
break;
case 7:
focus[ii].setColor(Color.rgb(0x66, 0x99, 0));
break;
}
focus[ii].setAntiAlias(antialias);
focus[ii].setStrokeWidth(densityScale * 3/mScale); // Used with global scaling
focus[ii].setStrokeCap(Paint.Cap.ROUND);
focus[ii].setStyle(Paint.Style.STROKE);
//focus[ii].setAlpha(42); // Larger means less opaque
focus2[ii] = new Paint(focus[ii]);
focus2[ii].setStrokeWidth(densityScale * 1.5f); // Used with screen scaling
}
}
// FIXME: Autoset when modifying viewmatrix, add methods for updating viewmatrix and autoset in these
private void setInvertM() {
assert viewmatrix.invert(inv);
viewmatrix.invert(inv);
}
// Convert from world coords (not mercator projected) to screen coords
public float[] world2view(double lon, double lat) {
float [] pts = new float[]{(float)lon*MapView.prescale, (float)GeoMath.latToMercator(lat)*MapView.prescale};
viewmatrix.mapPoints(pts);
return pts;
}
// Convert from view coords to world coords (not mercator projected)
public double[] view2world(float x, float y) {
pts[0] = x;
pts[1] = y;
double [] ptsd = new double[2];
setInvertM();
inv.mapPoints(pts);
ptsd[0] = pts[0]/MapView.prescale;
ptsd[1] = GeoMath.mercatorToLat(pts[1]/MapView.prescale);
return ptsd;
}
public void view2world(RectF rect) {
setInvertM();
pts[0] = rect.left;
pts[1] = rect.top;
inv.mapPoints(pts);
rect.left = pts[0]/MapView.prescale;
rect.top = (float) GeoMath.mercatorToLat(pts[1]/MapView.prescale);
pts[0] = rect.right;
pts[1] = rect.bottom;
inv.mapPoints(pts);
rect.right = pts[0]/MapView.prescale;
rect.bottom = (float) GeoMath.mercatorToLat(pts[1]/MapView.prescale);
}
boolean hasTag(OsmElement e, OsmElement multiPolyOuter, String key, String val) {
return ((multiPolyOuter==null && e.hasTag(key, val)) || (multiPolyOuter!=null && multiPolyOuter.hasTag(key, val)));
}
boolean hasTagKey(OsmElement e, OsmElement multiPolyOuter, String key) {
return ((multiPolyOuter==null && e.hasTagKey(key)) || (multiPolyOuter!=null && multiPolyOuter.hasTagKey(key)));
}
// Small hack
public Paint look, look2, nodelook;
public void getWayPaints(OsmElement e, OsmElement multiPolyOuter) {
look = look2 = nodelook = null;
if (hasTag(e, multiPolyOuter, "natural", "water")) {
if (getLayer()==0) {
look = water;
}
} else if (hasTag(e, multiPolyOuter, "natural", "scrub") || hasTag(e, multiPolyOuter, "landuse", "forest") ||
hasTag(e, multiPolyOuter, "landuse", "grass") || hasTagKey(e, multiPolyOuter, "natural")) {
if (getLayer()==0) {
look = nature;
}
} else if (hasTagKey(e, multiPolyOuter, "building")) {
if (getLayer()==0) {
look = buildings;
look2 = buildings2;
}
} else if (hasTag(e, multiPolyOuter, "highway", "residential") ||
hasTag(e, multiPolyOuter, "highway", "living_street") ||
hasTag(e, multiPolyOuter, "highway", "pedestrian")) {
if (getLayer()==1) {
look = waystroke;
} else if (getLayer()==2) {
if (e.mIsArea) {
look = wayfill2;
} else {
look = waystroke2;
}
}
} else if (hasTag(e, multiPolyOuter, "highway", "service")) {
if (getLayer()==1) {
look = service;
} else if (getLayer()==2) {
look = service2;
}
} else if (hasTag(e, multiPolyOuter, "highway", "secondary") || hasTag(e, multiPolyOuter, "highway", "primary") ||
hasTag(e, multiPolyOuter, "highway", "trunk") || hasTag(e, multiPolyOuter, "highway", "motorway")) {
if (getLayer()==1) {
look = waystrokesec;
} else if (getLayer()==2) {
look = waystrokesec2;
}
} else if (hasTag(e, multiPolyOuter, "highway", "tertiary") || hasTag(e, multiPolyOuter, "highway", "unclassified")) {
if (getLayer()==1) {
look = waystroketert;
} else if (getLayer()==2) {
look = waystroketert2;
}
} else if (hasTag(e, multiPolyOuter, "highway", "path") || hasTag(e, multiPolyOuter, "highway", "cycleway") ||
hasTag(e, multiPolyOuter, "highway", "footway")) {
if (getLayer()==1) {
look = path;
}
} else if (hasTag(e, multiPolyOuter, "railway", "rail")) {
if (getLayer()==1) {
look = rail;
} else if (getLayer()==1) {
look = rail2;
}
} else if (hasTag(e, multiPolyOuter, "highway", "track")) {
if (getLayer()==1) {
look = track;
}
} else if (multiPolyOuter==null) {
if (getLayer()==1) {
look = waystroke_undef;
nodelook = paint;
}
}
}
}

View file

@ -0,0 +1,15 @@
package dk.network42.osmfocus;
import android.app.Activity;
import android.os.Bundle;
public class SettingsActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new SettingsFragment())
.commit();
}
}

View file

@ -0,0 +1,66 @@
package dk.network42.osmfocus;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.MultiSelectListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
public class SettingsFragment extends PreferenceFragment implements OnSharedPreferenceChangeListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (getActivity() != null)
updateSummary(findPreference(key));
}
@Override
public void onResume() {
super.onResume();
for (int ii = 0; ii < getPreferenceScreen().getPreferenceCount(); ii++) {
Preference preference = getPreferenceScreen().getPreference(ii);
if (preference instanceof PreferenceGroup) {
PreferenceGroup preferenceGroup = (PreferenceGroup) preference;
for (int jj = 0; jj < preferenceGroup.getPreferenceCount(); jj++) {
updateSummary(preferenceGroup.getPreference(jj));
}
} else {
updateSummary(preference);
}
}
}
private void updateSummary(Preference p) {
if (p instanceof ListPreference) {
ListPreference listPref = (ListPreference) p;
p.setSummary(listPref.getEntry());
}
if (p instanceof EditTextPreference) {
EditTextPreference editTextPref = (EditTextPreference) p;
p.setSummary(editTextPref.getText());
}
if (p instanceof MultiSelectListPreference) {
EditTextPreference editTextPref = (EditTextPreference) p;
p.setSummary(editTextPref.getText());
}
if (p instanceof CheckBoxPreference) {
CheckBoxPreference chkPref = (CheckBoxPreference) p;
if (chkPref.isChecked()) {
p.setSummary(this.getString(R.string.info_enabled));
} else {
p.setSummary(this.getString(R.string.info_disabled));
}
}
}
}

View file

@ -0,0 +1,110 @@
package dk.network42.osmfocus;
import java.io.PrintWriter;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.location.Location;
import android.preference.PreferenceManager;
import android.util.Log;
public class SharedData {
private static final String TAG = "SharedData";
private static final String PREF_VER_KEY = "prefVersion";
public OsmDB mDb = new OsmDB();
public OsmTileProvider mTileLayerProvider;
public OsmTileLayerBm mTileLayer = null;
public OsmTileProvider mVectorLayerProvider;
public OsmTileLayerVector mVectorLayer = null;
public boolean mUseCompass = false;
boolean mDebugHUD = false;
public boolean mFollowGPS = true;
public double mLon, mLat; // View location
long mLocationUpdates = 0;
int mSatelitesVisible = 0;
int mSatelitesUsed = 0;
Location mPhyLocation = null; // Physical location
public String mLocProvider;
public double mAzimuth, mPitch, mRoll;
PaintConfig mPcfg = new PaintConfig();
final boolean mDeveloperMode = false;
static final int AUTODOWNLOAD_MANUAL = 1;
static final int AUTODOWNLOAD_AUTOMATIC1 = 2;
static final int AUTODOWNLOAD_AUTOMATIC2 = 3;
int mVectorAutoDownload = AUTODOWNLOAD_AUTOMATIC1;
boolean mShowPoiLines = true;
int mPoisToShow = 0;
String mOsmServerAgentName = new String("OSMfocus");
Context mCtx;
public void update(Context ctx) {
mCtx = ctx;
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(ctx);
mVectorAutoDownload = Integer.parseInt(sharedPrefs.getString("pref_autoload", "2"));
mShowPoiLines = sharedPrefs.getBoolean("pref_poilines", false);
int numpoi = Integer.parseInt(sharedPrefs.getString("pref_poinum", "0"));
boolean largescreen = (ctx.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE;
boolean landscape = (ctx.getResources().getConfiguration().orientation & Configuration.ORIENTATION_LANDSCAPE) != 0;
//Log.d(TAG, "Pref:POI num=" + numpoi+" isLargeScreen="+largescreen+" landscape="+landscape);
if (numpoi == 0) { // Auto
if (largescreen || landscape) {
mPoisToShow = 8;
} else {
mPoisToShow = 4;
}
Log.d(TAG, "Auto POI num=" + mPoisToShow);
} else {
mPoisToShow = numpoi;
}
int backtype = Integer.parseInt(sharedPrefs.getString("pref_backmaptype", "1"));
if (mPcfg.mBackMapType != backtype) {
mPcfg.mBackMapType = backtype;
mTileLayer.setProviderUrl(OsmTileLayer.urlFromType(mPcfg.mBackMapType));
}
mPcfg.update(ctx);
}
public void printState(PrintWriter pw) {
pw.print("Loc="+mLon+","+mLat);
pw.print("PhyLoc="+mPhyLocation);
}
static void checkAppUpdate(Context ctx) {
// SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
// int ver = getAppVersion(ctx);
// int prefver = prefs.getInt(PREF_VER_KEY, 0);
// SharedPreferences.Editor ed = prefs.edit();
// if (prefver >= 0) {
// if (ver==2) {
// Log.d(TAG, "Do pref update, have "+prefver+" wants "+ver);
// ed.remove("pref_autoload");
// ed.apply();
// }
// }
// ed.putInt(PREF_VER_KEY, prefver);
// ed.apply();
}
static int getAppVersion(Context ctx) {
int ver = -1;
try {
ver = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0).versionCode;
} catch (NameNotFoundException e) {
Log.e(TAG, "Could not find package name");
}
return ver;
}
}

View file

@ -0,0 +1,32 @@
package dk.network42.osmfocus;
import android.util.SparseArray;
public class SparseMatrix<E> {
private SparseArray<SparseArray<E>> rows = new SparseArray<SparseArray<E>>();
public SparseMatrix() {
}
public void put(int x, int y, E elem) {
SparseArray<E> a = rows.get(y);
if (a==null) {
a = new SparseArray<E>();
rows.put(y, a);
}
a.put(x, elem);
}
public E get(int x, int y) {
SparseArray<E> a = rows.get(y);
if (a==null)
return null;
return a.get(x);
}
public void delete(int x, int y) {
SparseArray<E> a = rows.get(y);
if (a!=null)
a.delete(x);
}
}