Open sourcing OSMfocus v0.1.1r1
This commit is contained in:
parent
914c54f80d
commit
5b781c989a
40
README.md
40
README.md
|
@ -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
BIN
images/featuregfx.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 543 KiB |
BIN
images/icon.png
Normal file
BIN
images/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 179 KiB |
BIN
images/screen01.png
Normal file
BIN
images/screen01.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 384 KiB |
BIN
images/screen02.png
Normal file
BIN
images/screen02.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 202 KiB |
BIN
images/screen03.png
Normal file
BIN
images/screen03.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 266 KiB |
BIN
images/screen04.png
Normal file
BIN
images/screen04.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 406 KiB |
11
res/layout/activity_main.xml
Normal file
11
res/layout/activity_main.xml
Normal 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
13
res/menu/main.xml
Normal 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>
|
15
res/menu/main_activity_actions.xml
Normal file
15
res/menu/main_activity_actions.xml
Normal 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
62
res/values/arrays.xml
Normal 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
7
res/values/dimens.xml
Normal 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
39
res/values/strings.xml
Normal 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">© OpenStreetMap contributors</string>
|
||||
</resources>
|
20
res/values/styles.xml
Normal file
20
res/values/styles.xml
Normal 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
39
res/xml/preferences.xml
Normal 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>
|
63
src/dk/network42/osmfocus/CustomExceptionHandler.java
Normal file
63
src/dk/network42/osmfocus/CustomExceptionHandler.java
Normal 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");
|
||||
}
|
||||
}
|
15
src/dk/network42/osmfocus/Filter.java
Normal file
15
src/dk/network42/osmfocus/Filter.java
Normal 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;
|
||||
// }
|
||||
}
|
||||
}
|
45
src/dk/network42/osmfocus/GeoBBox.java
Normal file
45
src/dk/network42/osmfocus/GeoBBox.java
Normal 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;
|
||||
}
|
||||
}
|
120
src/dk/network42/osmfocus/GeoMath.java
Normal file
120
src/dk/network42/osmfocus/GeoMath.java
Normal 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};
|
||||
}
|
||||
}
|
705
src/dk/network42/osmfocus/MainActivity.java
Normal file
705
src/dk/network42/osmfocus/MainActivity.java
Normal 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) {
|
||||
}
|
||||
}
|
14
src/dk/network42/osmfocus/MapLayer.java
Normal file
14
src/dk/network42/osmfocus/MapLayer.java
Normal 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) {}
|
||||
}
|
165
src/dk/network42/osmfocus/MapView.java
Normal file
165
src/dk/network42/osmfocus/MapView.java
Normal 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;
|
||||
}
|
||||
}
|
45
src/dk/network42/osmfocus/OsmBounds.java
Normal file
45
src/dk/network42/osmfocus/OsmBounds.java
Normal 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);
|
||||
}
|
||||
}
|
396
src/dk/network42/osmfocus/OsmDB.java
Normal file
396
src/dk/network42/osmfocus/OsmDB.java
Normal 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()+"}";
|
||||
}
|
||||
}
|
317
src/dk/network42/osmfocus/OsmElement.java
Normal file
317
src/dk/network42/osmfocus/OsmElement.java
Normal 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; }
|
||||
}
|
15
src/dk/network42/osmfocus/OsmException.java
Normal file
15
src/dk/network42/osmfocus/OsmException.java
Normal 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);
|
||||
}
|
||||
}
|
86
src/dk/network42/osmfocus/OsmNode.java
Normal file
86
src/dk/network42/osmfocus/OsmNode.java
Normal 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);
|
||||
}
|
||||
}
|
8
src/dk/network42/osmfocus/OsmParseException.java
Normal file
8
src/dk/network42/osmfocus/OsmParseException.java
Normal file
|
@ -0,0 +1,8 @@
|
|||
package dk.network42.osmfocus;
|
||||
|
||||
public class OsmParseException extends Exception {
|
||||
|
||||
public OsmParseException(final String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
171
src/dk/network42/osmfocus/OsmParser.java
Normal file
171
src/dk/network42/osmfocus/OsmParser.java
Normal 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); }
|
||||
}
|
143
src/dk/network42/osmfocus/OsmRelation.java
Normal file
143
src/dk/network42/osmfocus/OsmRelation.java
Normal 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; }
|
||||
}
|
124
src/dk/network42/osmfocus/OsmServer.java
Normal file
124
src/dk/network42/osmfocus/OsmServer.java
Normal 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;
|
||||
}
|
||||
}
|
14
src/dk/network42/osmfocus/OsmServerException.java
Normal file
14
src/dk/network42/osmfocus/OsmServerException.java
Normal 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;
|
||||
}
|
||||
}
|
268
src/dk/network42/osmfocus/OsmTile.java
Normal file
268
src/dk/network42/osmfocus/OsmTile.java
Normal 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);
|
||||
}
|
||||
}
|
196
src/dk/network42/osmfocus/OsmTileLayer.java
Normal file
196
src/dk/network42/osmfocus/OsmTileLayer.java
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
35
src/dk/network42/osmfocus/OsmTileLayerBm.java
Normal file
35
src/dk/network42/osmfocus/OsmTileLayerBm.java
Normal 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);
|
||||
}
|
||||
}
|
221
src/dk/network42/osmfocus/OsmTileLayerVector.java
Normal file
221
src/dk/network42/osmfocus/OsmTileLayerVector.java
Normal 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
|
||||
}
|
||||
|
||||
}
|
119
src/dk/network42/osmfocus/OsmTileProvider.java
Normal file
119
src/dk/network42/osmfocus/OsmTileProvider.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
95
src/dk/network42/osmfocus/OsmTileVector.java
Normal file
95
src/dk/network42/osmfocus/OsmTileVector.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
138
src/dk/network42/osmfocus/OsmWay.java
Normal file
138
src/dk/network42/osmfocus/OsmWay.java
Normal 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]);
|
||||
}
|
||||
}
|
405
src/dk/network42/osmfocus/PaintConfig.java
Normal file
405
src/dk/network42/osmfocus/PaintConfig.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
src/dk/network42/osmfocus/SettingsActivity.java
Normal file
15
src/dk/network42/osmfocus/SettingsActivity.java
Normal 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();
|
||||
}
|
||||
}
|
66
src/dk/network42/osmfocus/SettingsFragment.java
Normal file
66
src/dk/network42/osmfocus/SettingsFragment.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
110
src/dk/network42/osmfocus/SharedData.java
Normal file
110
src/dk/network42/osmfocus/SharedData.java
Normal 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;
|
||||
}
|
||||
}
|
32
src/dk/network42/osmfocus/SparseMatrix.java
Normal file
32
src/dk/network42/osmfocus/SparseMatrix.java
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue