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
|
# 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