From 436b66be82167055844dfa39ed452ab07a22f279 Mon Sep 17 00:00:00 2001 From: Emux <devemux86@gmail.com> Date: Thu, 21 Jul 2016 20:22:22 +0300 Subject: [PATCH] vtm-app: revive / update with latest VTM, closes #90 --- settings.gradle | 1 + vtm-app/AndroidManifest.xml | 65 ++ vtm-app/COPYING | 674 ++++++++++++++++++ vtm-app/COPYING.LESSER | 165 +++++ vtm-app/assets/globe2.png | Bin 0 -> 6256 bytes vtm-app/assets/info.xml | 25 + vtm-app/build.gradle | 47 ++ vtm-app/project.properties | 14 + vtm-app/res/anim/slide_left.xml | 9 + vtm-app/res/anim/slide_left2.xml | 9 + vtm-app/res/anim/slide_right.xml | 9 + vtm-app/res/anim/slide_right2.xml | 9 + .../res/drawable-hdpi/ic_action_search.png | Bin 0 -> 3120 bytes vtm-app/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 3437 bytes vtm-app/res/drawable-hdpi/ic_layers.png | Bin 0 -> 3340 bytes .../res/drawable-mdpi/bonuspack_bubble.9.png | Bin 0 -> 741 bytes vtm-app/res/drawable-mdpi/btn_snap_normal.png | Bin 0 -> 1755 bytes .../res/drawable-mdpi/btn_snap_pressed.png | Bin 0 -> 1897 bytes .../res/drawable-mdpi/btn_snap_selected.png | Bin 0 -> 1648 bytes vtm-app/res/drawable-mdpi/car.png | Bin 0 -> 540 bytes vtm-app/res/drawable-mdpi/delete.png | Bin 0 -> 2645 bytes vtm-app/res/drawable-mdpi/delete_pressed.png | Bin 0 -> 2574 bytes .../res/drawable-mdpi/file_picker_back.png | Bin 0 -> 1951 bytes .../res/drawable-mdpi/file_picker_file.png | Bin 0 -> 1024 bytes .../res/drawable-mdpi/file_picker_folder.png | Bin 0 -> 1460 bytes .../res/drawable-mdpi/ic_action_search.png | Bin 0 -> 3030 bytes vtm-app/res/drawable-mdpi/ic_continue.png | Bin 0 -> 414 bytes vtm-app/res/drawable-mdpi/ic_empty.png | Bin 0 -> 240 bytes vtm-app/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 1396 bytes vtm-app/res/drawable-mdpi/ic_layers.png | Bin 0 -> 1833 bytes .../res/drawable-mdpi/ic_menu_mylocation.png | Bin 0 -> 1409 bytes vtm-app/res/drawable-mdpi/ic_menu_options.png | Bin 0 -> 1339 bytes vtm-app/res/drawable-mdpi/info.png | Bin 0 -> 1333 bytes vtm-app/res/drawable-mdpi/info_window.9.png | Bin 0 -> 416 bytes vtm-app/res/drawable-mdpi/moreinfo_arrow.png | Bin 0 -> 1500 bytes .../drawable-mdpi/moreinfo_arrow_pressed.png | Bin 0 -> 1582 bytes vtm-app/res/drawable-mdpi/no.png | Bin 0 -> 443 bytes vtm-app/res/drawable-mdpi/plane.png | Bin 0 -> 407 bytes vtm-app/res/drawable-mdpi/time.png | Bin 0 -> 597 bytes vtm-app/res/drawable-mdpi/yes.png | Bin 0 -> 377 bytes vtm-app/res/drawable-nodpi/direction.png | Bin 0 -> 1826 bytes vtm-app/res/drawable-nodpi/ic_launcher.png | Bin 0 -> 3907 bytes .../res/drawable-nodpi/marker_departure.png | Bin 0 -> 1413 bytes .../res/drawable-nodpi/marker_destination.png | Bin 0 -> 1160 bytes vtm-app/res/drawable-nodpi/marker_node.png | Bin 0 -> 611 bytes .../res/drawable-nodpi/marker_poi_default.png | Bin 0 -> 610 bytes .../res/drawable-nodpi/marker_poi_flickr.png | Bin 0 -> 490 bytes .../drawable-nodpi/marker_poi_picasa_24.png | Bin 0 -> 2295 bytes .../marker_poi_wikipedia_16.png | Bin 0 -> 1474 bytes .../marker_poi_wikipedia_32.png | Bin 0 -> 2117 bytes vtm-app/res/drawable-nodpi/marker_via.png | Bin 0 -> 1119 bytes vtm-app/res/drawable-nodpi/pin.png | Bin 0 -> 1725 bytes .../res/drawable-xhdpi/ic_action_search.png | Bin 0 -> 3199 bytes vtm-app/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 4591 bytes vtm-app/res/drawable-xhdpi/ic_layers.png | Bin 0 -> 4940 bytes vtm-app/res/drawable/arrow.png | Bin 0 -> 10033 bytes vtm-app/res/drawable/bkg_compass.png | Bin 0 -> 40821 bytes vtm-app/res/drawable/btn_delete.xml | 6 + vtm-app/res/drawable/btn_moreinfo.xml | 6 + vtm-app/res/drawable/compass.png | Bin 0 -> 1826 bytes vtm-app/res/drawable/globe2.png | Bin 0 -> 6256 bytes vtm-app/res/drawable/person.png | Bin 0 -> 1531 bytes vtm-app/res/layout/activity_file_picker.xml | 9 + vtm-app/res/layout/activity_tilemap.xml | 126 ++++ vtm-app/res/layout/bonuspack_bubble.xml | 67 ++ .../res/layout/dialog_enter_coordinates.xml | 59 ++ vtm-app/res/layout/dialog_info_map_file.xml | 155 ++++ vtm-app/res/layout/item_layout.xml | 34 + vtm-app/res/layout/items_list.xml | 80 +++ vtm-app/res/layout/itinerary_bubble.xml | 61 ++ vtm-app/res/layout/seek_bar_preference.xml | 61 ++ vtm-app/res/menu/map_menu.xml | 22 + vtm-app/res/menu/options_menu.xml | 106 +++ vtm-app/res/menu/poi_menu.xml | 10 + vtm-app/res/values-v11/styles.xml | 5 + vtm-app/res/values-v14/styles.xml | 5 + vtm-app/res/values/poi_tags.xml | 102 +++ vtm-app/res/values/route.xml | 4 + vtm-app/res/values/settings.xml | 115 +++ vtm-app/res/values/strings.xml | 68 ++ vtm-app/res/values/styles.xml | 5 + vtm-app/res/xml/preferences.xml | 126 ++++ vtm-app/src/android-logger.properties | 1 + vtm-app/src/org/oscim/app/App.java | 70 ++ .../src/org/oscim/app/ConnectionHandler.java | 31 + vtm-app/src/org/oscim/app/FileUtils.java | 63 ++ vtm-app/src/org/oscim/app/InfoView.java | 47 ++ vtm-app/src/org/oscim/app/MapActivity.java | 125 ++++ vtm-app/src/org/oscim/app/MapLayers.java | 189 +++++ vtm-app/src/org/oscim/app/POIActivity.java | 309 ++++++++ vtm-app/src/org/oscim/app/POISearch.java | 320 +++++++++ vtm-app/src/org/oscim/app/RouteSearch.java | 452 ++++++++++++ vtm-app/src/org/oscim/app/TileMap.java | 518 ++++++++++++++ .../app/filefilter/FilterByFileExtension.java | 47 ++ .../oscim/app/filefilter/ValidFileFilter.java | 29 + .../oscim/app/filefilter/ValidMapFile.java | 42 ++ .../app/filefilter/ValidRenderTheme.java | 72 ++ .../org/oscim/app/filepicker/FilePicker.java | 262 +++++++ .../app/filepicker/FilePickerIconAdapter.java | 114 +++ .../src/org/oscim/app/location/Compass.java | 392 ++++++++++ .../oscim/app/location/LocationDialog.java | 120 ++++ .../oscim/app/location/LocationHandler.java | 236 ++++++ .../oscim/app/location/LocationOverlay.java | 331 +++++++++ .../app/preferences/CacheSizePreference.java | 219 ++++++ .../app/preferences/EditPreferences.java | 68 ++ .../app/preferences/SeekBarPreference.java | 153 ++++ .../oscim/overlay/DistanceTouchOverlay.java | 182 +++++ .../osmdroid/location/FlickrPOIProvider.java | 163 +++++ .../osmdroid/location/FourSquareProvider.java | 186 +++++ .../location/GeoNamesPOIProvider.java | 215 ++++++ .../osmdroid/location/GeocoderNominatim.java | 208 ++++++ .../location/NominatimPOIProvider.java | 192 +++++ .../location/OverpassPOIProvider.java | 67 ++ vtm-app/src/org/osmdroid/location/POI.java | 205 ++++++ .../org/osmdroid/location/POIProvider.java | 10 + .../osmdroid/location/PicasaPOIProvider.java | 161 +++++ .../osmdroid/overlays/DefaultInfoWindow.java | 99 +++ .../osmdroid/overlays/ExtendedMarkerItem.java | 123 ++++ .../src/org/osmdroid/overlays/InfoWindow.java | 135 ++++ .../overlays/ItemizedOverlayWithBubble.java | 195 +++++ .../osmdroid/overlays/MapEventsReceiver.java | 34 + vtm-app/src/org/osmdroid/routing/Route.java | 255 +++++++ .../src/org/osmdroid/routing/RouteLeg.java | 85 +++ .../src/org/osmdroid/routing/RouteNode.java | 79 ++ .../org/osmdroid/routing/RouteProvider.java | 67 ++ .../routing/provider/GoogleRouteProvider.java | 235 ++++++ .../provider/MapQuestRouteProvider.java | 289 ++++++++ .../routing/provider/OSRMRouteProvider.java | 294 ++++++++ .../org/osmdroid/utils/BonusPackHelper.java | 112 +++ .../osmdroid/utils/DouglasPeuckerReducer.java | 141 ++++ .../org/osmdroid/utils/HttpConnection.java | 119 ++++ .../src/org/osmdroid/utils/MathConstants.java | 22 + .../org/osmdroid/utils/PolylineEncoder.java | 94 +++ 133 files changed, 10436 insertions(+) create mode 100644 vtm-app/AndroidManifest.xml create mode 100644 vtm-app/COPYING create mode 100644 vtm-app/COPYING.LESSER create mode 100644 vtm-app/assets/globe2.png create mode 100644 vtm-app/assets/info.xml create mode 100644 vtm-app/build.gradle create mode 100644 vtm-app/project.properties create mode 100644 vtm-app/res/anim/slide_left.xml create mode 100644 vtm-app/res/anim/slide_left2.xml create mode 100644 vtm-app/res/anim/slide_right.xml create mode 100644 vtm-app/res/anim/slide_right2.xml create mode 100644 vtm-app/res/drawable-hdpi/ic_action_search.png create mode 100644 vtm-app/res/drawable-hdpi/ic_launcher.png create mode 100644 vtm-app/res/drawable-hdpi/ic_layers.png create mode 100644 vtm-app/res/drawable-mdpi/bonuspack_bubble.9.png create mode 100644 vtm-app/res/drawable-mdpi/btn_snap_normal.png create mode 100644 vtm-app/res/drawable-mdpi/btn_snap_pressed.png create mode 100644 vtm-app/res/drawable-mdpi/btn_snap_selected.png create mode 100644 vtm-app/res/drawable-mdpi/car.png create mode 100644 vtm-app/res/drawable-mdpi/delete.png create mode 100644 vtm-app/res/drawable-mdpi/delete_pressed.png create mode 100644 vtm-app/res/drawable-mdpi/file_picker_back.png create mode 100644 vtm-app/res/drawable-mdpi/file_picker_file.png create mode 100644 vtm-app/res/drawable-mdpi/file_picker_folder.png create mode 100644 vtm-app/res/drawable-mdpi/ic_action_search.png create mode 100644 vtm-app/res/drawable-mdpi/ic_continue.png create mode 100644 vtm-app/res/drawable-mdpi/ic_empty.png create mode 100644 vtm-app/res/drawable-mdpi/ic_launcher.png create mode 100644 vtm-app/res/drawable-mdpi/ic_layers.png create mode 100644 vtm-app/res/drawable-mdpi/ic_menu_mylocation.png create mode 100644 vtm-app/res/drawable-mdpi/ic_menu_options.png create mode 100644 vtm-app/res/drawable-mdpi/info.png create mode 100644 vtm-app/res/drawable-mdpi/info_window.9.png create mode 100644 vtm-app/res/drawable-mdpi/moreinfo_arrow.png create mode 100644 vtm-app/res/drawable-mdpi/moreinfo_arrow_pressed.png create mode 100644 vtm-app/res/drawable-mdpi/no.png create mode 100644 vtm-app/res/drawable-mdpi/plane.png create mode 100644 vtm-app/res/drawable-mdpi/time.png create mode 100644 vtm-app/res/drawable-mdpi/yes.png create mode 100644 vtm-app/res/drawable-nodpi/direction.png create mode 100644 vtm-app/res/drawable-nodpi/ic_launcher.png create mode 100644 vtm-app/res/drawable-nodpi/marker_departure.png create mode 100644 vtm-app/res/drawable-nodpi/marker_destination.png create mode 100644 vtm-app/res/drawable-nodpi/marker_node.png create mode 100644 vtm-app/res/drawable-nodpi/marker_poi_default.png create mode 100644 vtm-app/res/drawable-nodpi/marker_poi_flickr.png create mode 100644 vtm-app/res/drawable-nodpi/marker_poi_picasa_24.png create mode 100644 vtm-app/res/drawable-nodpi/marker_poi_wikipedia_16.png create mode 100644 vtm-app/res/drawable-nodpi/marker_poi_wikipedia_32.png create mode 100644 vtm-app/res/drawable-nodpi/marker_via.png create mode 100644 vtm-app/res/drawable-nodpi/pin.png create mode 100644 vtm-app/res/drawable-xhdpi/ic_action_search.png create mode 100644 vtm-app/res/drawable-xhdpi/ic_launcher.png create mode 100644 vtm-app/res/drawable-xhdpi/ic_layers.png create mode 100644 vtm-app/res/drawable/arrow.png create mode 100644 vtm-app/res/drawable/bkg_compass.png create mode 100644 vtm-app/res/drawable/btn_delete.xml create mode 100644 vtm-app/res/drawable/btn_moreinfo.xml create mode 100644 vtm-app/res/drawable/compass.png create mode 100644 vtm-app/res/drawable/globe2.png create mode 100644 vtm-app/res/drawable/person.png create mode 100644 vtm-app/res/layout/activity_file_picker.xml create mode 100644 vtm-app/res/layout/activity_tilemap.xml create mode 100644 vtm-app/res/layout/bonuspack_bubble.xml create mode 100644 vtm-app/res/layout/dialog_enter_coordinates.xml create mode 100644 vtm-app/res/layout/dialog_info_map_file.xml create mode 100644 vtm-app/res/layout/item_layout.xml create mode 100644 vtm-app/res/layout/items_list.xml create mode 100644 vtm-app/res/layout/itinerary_bubble.xml create mode 100644 vtm-app/res/layout/seek_bar_preference.xml create mode 100644 vtm-app/res/menu/map_menu.xml create mode 100644 vtm-app/res/menu/options_menu.xml create mode 100644 vtm-app/res/menu/poi_menu.xml create mode 100644 vtm-app/res/values-v11/styles.xml create mode 100644 vtm-app/res/values-v14/styles.xml create mode 100644 vtm-app/res/values/poi_tags.xml create mode 100644 vtm-app/res/values/route.xml create mode 100644 vtm-app/res/values/settings.xml create mode 100755 vtm-app/res/values/strings.xml create mode 100644 vtm-app/res/values/styles.xml create mode 100644 vtm-app/res/xml/preferences.xml create mode 100644 vtm-app/src/android-logger.properties create mode 100644 vtm-app/src/org/oscim/app/App.java create mode 100644 vtm-app/src/org/oscim/app/ConnectionHandler.java create mode 100644 vtm-app/src/org/oscim/app/FileUtils.java create mode 100644 vtm-app/src/org/oscim/app/InfoView.java create mode 100644 vtm-app/src/org/oscim/app/MapActivity.java create mode 100644 vtm-app/src/org/oscim/app/MapLayers.java create mode 100644 vtm-app/src/org/oscim/app/POIActivity.java create mode 100644 vtm-app/src/org/oscim/app/POISearch.java create mode 100644 vtm-app/src/org/oscim/app/RouteSearch.java create mode 100755 vtm-app/src/org/oscim/app/TileMap.java create mode 100644 vtm-app/src/org/oscim/app/filefilter/FilterByFileExtension.java create mode 100644 vtm-app/src/org/oscim/app/filefilter/ValidFileFilter.java create mode 100644 vtm-app/src/org/oscim/app/filefilter/ValidMapFile.java create mode 100644 vtm-app/src/org/oscim/app/filefilter/ValidRenderTheme.java create mode 100755 vtm-app/src/org/oscim/app/filepicker/FilePicker.java create mode 100755 vtm-app/src/org/oscim/app/filepicker/FilePickerIconAdapter.java create mode 100644 vtm-app/src/org/oscim/app/location/Compass.java create mode 100644 vtm-app/src/org/oscim/app/location/LocationDialog.java create mode 100644 vtm-app/src/org/oscim/app/location/LocationHandler.java create mode 100644 vtm-app/src/org/oscim/app/location/LocationOverlay.java create mode 100644 vtm-app/src/org/oscim/app/preferences/CacheSizePreference.java create mode 100644 vtm-app/src/org/oscim/app/preferences/EditPreferences.java create mode 100644 vtm-app/src/org/oscim/app/preferences/SeekBarPreference.java create mode 100644 vtm-app/src/org/oscim/overlay/DistanceTouchOverlay.java create mode 100644 vtm-app/src/org/osmdroid/location/FlickrPOIProvider.java create mode 100644 vtm-app/src/org/osmdroid/location/FourSquareProvider.java create mode 100644 vtm-app/src/org/osmdroid/location/GeoNamesPOIProvider.java create mode 100644 vtm-app/src/org/osmdroid/location/GeocoderNominatim.java create mode 100644 vtm-app/src/org/osmdroid/location/NominatimPOIProvider.java create mode 100644 vtm-app/src/org/osmdroid/location/OverpassPOIProvider.java create mode 100644 vtm-app/src/org/osmdroid/location/POI.java create mode 100644 vtm-app/src/org/osmdroid/location/POIProvider.java create mode 100644 vtm-app/src/org/osmdroid/location/PicasaPOIProvider.java create mode 100644 vtm-app/src/org/osmdroid/overlays/DefaultInfoWindow.java create mode 100644 vtm-app/src/org/osmdroid/overlays/ExtendedMarkerItem.java create mode 100644 vtm-app/src/org/osmdroid/overlays/InfoWindow.java create mode 100644 vtm-app/src/org/osmdroid/overlays/ItemizedOverlayWithBubble.java create mode 100644 vtm-app/src/org/osmdroid/overlays/MapEventsReceiver.java create mode 100644 vtm-app/src/org/osmdroid/routing/Route.java create mode 100644 vtm-app/src/org/osmdroid/routing/RouteLeg.java create mode 100644 vtm-app/src/org/osmdroid/routing/RouteNode.java create mode 100644 vtm-app/src/org/osmdroid/routing/RouteProvider.java create mode 100644 vtm-app/src/org/osmdroid/routing/provider/GoogleRouteProvider.java create mode 100644 vtm-app/src/org/osmdroid/routing/provider/MapQuestRouteProvider.java create mode 100644 vtm-app/src/org/osmdroid/routing/provider/OSRMRouteProvider.java create mode 100644 vtm-app/src/org/osmdroid/utils/BonusPackHelper.java create mode 100644 vtm-app/src/org/osmdroid/utils/DouglasPeuckerReducer.java create mode 100644 vtm-app/src/org/osmdroid/utils/HttpConnection.java create mode 100644 vtm-app/src/org/osmdroid/utils/MathConstants.java create mode 100644 vtm-app/src/org/osmdroid/utils/PolylineEncoder.java diff --git a/settings.gradle b/settings.gradle index 0237e353..6babee72 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,3 +16,4 @@ include ':vtm-android-gdx' include ':vtm-jeo' include ':vtm-playground' include ':vtm-ios' +include ':vtm-app' diff --git a/vtm-app/AndroidManifest.xml b/vtm-app/AndroidManifest.xml new file mode 100644 index 00000000..3c4c22ad --- /dev/null +++ b/vtm-app/AndroidManifest.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.oscim.app" + android:installLocation="auto" + android:versionCode="11" + android:versionName="0.6.0"> + + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.VIBRATE" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + + <uses-sdk + android:minSdkVersion="10" + android:targetSdkVersion="22" /> + + <application + android:name=".App" + android:allowBackup="true" + android:icon="@drawable/globe2" + android:label="@string/application_name"> + + <!-- android:theme="@style/Theme.TileMap" --> + <activity + android:name=".TileMap" + android:configChanges="keyboardHidden|orientation|screenSize" + android:launchMode="singleTask"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="geo" /> + </intent-filter> + </activity> + <activity + android:name=".preferences.EditPreferences" + android:configChanges="keyboardHidden|orientation|screenSize" /> + <activity + android:name=".filepicker.FilePicker" + android:configChanges="keyboardHidden|orientation|screenSize" /> + <activity + android:name=".InfoView" + android:configChanges="keyboardHidden|orientation|screenSize" /> + <activity + android:name=".POIActivity" + android:configChanges="keyboardHidden|orientation|screenSize" + android:launchMode="singleTop" + android:windowSoftInputMode="stateHidden" /> + + <!-- <receiver + android:name=".ConnectionHandler" + android:label="NetworkConnection" > + <intent-filter> + <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> + </intent-filter> + </receiver> --> + </application> + +</manifest> diff --git a/vtm-app/COPYING b/vtm-app/COPYING new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/vtm-app/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. diff --git a/vtm-app/COPYING.LESSER b/vtm-app/COPYING.LESSER new file mode 100644 index 00000000..65c5ca88 --- /dev/null +++ b/vtm-app/COPYING.LESSER @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/vtm-app/assets/globe2.png b/vtm-app/assets/globe2.png new file mode 100644 index 0000000000000000000000000000000000000000..6d788024b03350e799951cc58809c36ee88b905c GIT binary patch literal 6256 zcmV-$7?0<PP)<h;3K|Lk000e1NJLTq002+`002-31^@s6juG;$00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru-2)02CN;)w%@+Uw7yU^@ zK~#9!<y?E1995P7om*91-P6-E)AP!c$3RRV0@1J#<gp<p0W!)8tGKRy2<z^O?uvqd z_}~>pML~2GWl>xMDy;YcOCV7RhKE21NqB@MFN}daW+pS!&*^tn-Lrr6t(iNOPBKX* z$*|P--LHFkx~l8`>O6nv+)H?u9yi{2BLTp=b?Yb|k5e=n75RLg03el0As&wd0HV<- z=ks})N~L)E^y$2C;X(jl|2oFI6})xp){*PFqP4ZvU}mFSE*k&@03n1hOw%L)!f_m7 znkF?jH&Zg1#F{m0@NPdy0c8RR0f+#I0%*AAa!l-6fAq!4E#tntV|~XbPdOPa02<W) zBkKExx(2-)fz!bYD_|M{H0Qf!KFG<5%!wA}WL&*Y;OzF3?*h;qZ$u-2Ismo~BE7=^ z*xSK-IY$T31YotdBGk3%gKLE7p9Gn8h>IqZM_@xjgk3^3RCb$R47WXZJ^&YhqlO1S zigy@zw1<Jcd?u9Sm;fv#N!HelpSxceG>KRMfW=?}BtR^{2r-ClvwT`+$Az~l6%MIu z7ywAT!&J}Eq(>iy8X^Fo(9YAr3jwg6UH+BhLZQS50ncGnlwi34y8t@?R{*XM$Z;B? z+yD9F7oy!i*Z(c?4gijH0IZe?rUr(g{;mb)urYb^WMlgD>4tW_(6lK{3yAZ8954w5 z5K17F0d_#B04M_}0y1wh=W+el&Y!0w*LNnud<Uqk3BU*+In69>-AHvOoEE<6KYn(q zX;}@GVzKY~i~sIrC(-Q!kje(i`Sa%!04!U!%=54F*kg}1nx@$}dGh4bZQDL;@#4il zBl+iJSY`mu0w@4nR#M2-pH>!?$s>XVcwOU_^YH*w0J!Em06Y_bS=zeM_|FZQ&&2BM zFDrO74n&o3ea!s%6D#_9y1TEw>eM5iS2K5)En6m6uU_5o_~Va%$~4Uj0lY^D(Fy=k z%7W{<8yXrK+LNhVqO~atpg`afSHY72Ndg3e90CIb$zX@Ndom^Ei5*`8#Tb@ALyOhy zyWU7%D((84`YMEkgWlc>(g}wWy^au}wh6!f_VPDQ>Fn%`Igax?&+~duKKbMwhGBfJ zQmMR{OeVj)V#SIN5YZIFFix{=``<e|JBzDc3BO6Q0<h~cJTPPxK<4UumP*(h7v$i9 zhs{k&g$JDojZx_nnx_nhkC<9_`!nlS4M-!MaTrAjc9~!h5Eek4flwoy?3GhP=NF5` zZ};@{Jl4_C@x10#{LsZ#v0oHG3_z^=&G+3juDQ1Z$W`qhJTOZT2`~!)1`}EIzw7N4 zC!X2&Jpg@bqyXg2F{-+THevhr|M}BLk`+m5X>i6N%6m*M0F%HrF*R6JDUdk&n-46V z7Kub|QP-*0;zHdkyGpD8z?go@t3U1D_?}xrre`y=N8}iQWM(43^oT;n&i-)EiD!1- ztIlV2{+NmbDG=!DAxGFEv}UEKq+KBCFwzc?c7Tj}O;wl%DqN1Wji08ZE3D2Z<y;(P z^a}uT0I~ow0P?9ca_v*zym?!9^ERS-1Bd_=CZY(4>Ww$vY~C<_TKDbh{4#)&dUXLv za}0tf0w9Je+4R5wwbkX=$Q3<8QC6*$Lx3c}^cW$5$P14?uuVyANds*#YtW>-qCOJ< zL@p0;)CueFyz=t1k2>MF-ecQZou=(Oti;Q&w{5!Tm(O$wfxMI$P@oTJ05TmyfJPGU z3Elp6c6PSifB*eoZ)$4#)jz#BaBteIE9X4nmL<buf=S?z@Tfs>eM_OQ>u0kkUaur9 zqok}D01%oztW$Y#3xGBNP0B9TssFo5Vg~>u0rUds2ar~uRRB2VVFE4aFes2kaG_AR zDiVo&wOA~^3c$8jJp2oJ`agVyV7>*0FsgAHiE^=l1e?Ym@A-u~*Hsc&)Bu!$JlWK2 zp{L&a>KOzuqX0~(_c`sM925zp-Kb`*q<;{&*|TSx&ph*t*VEH;{J3%BRsz5)ue|c{ znKNhh0vHdV`GPC1pH`fE=|xVsvB`+VE1XT5b){7AwjW)w=#?jzZdX#$rz9n(0P$)7 zNLq*#)mWLBW<UK0;j3<5jY0c&FaQjC(prgq@cZn4?l{g}rfGh<P$=9HjYfYAAZD6o z+;!b%0L?x(ckU&hZ+GpwaJZbyF1h#aKBcY+06hxCl#;%Z1|HXteBV(;6mVhnx8VnX z>KY|Yn)R$_Q~|7&y9QOW7T9w0=FLZRbabo$V7&V3tLMy|Idg|bU`A9((xjxMUh}ZN zlw_3D^(r89N>ZEvc(tglS_FmvVBNs*f)9j78N49#Ayz*7rSUUPnsm8kHJuKW+gyoN zxoq#FEt9^v1OPIb%tf(S>?e-n{MxqduWNLX4!o@ezftwKPyp$6Dy@0RN)73&b<fb4 zoMCp@&#l$)H1oszV(j<D8JL=F5xM?bzZQ)YuBr-&3os9U&Ewc+Gj`os>2&%s$8j=k zZEcH{`jBSa{U<IoY4jyiXcCxHz!f#gbG3=`KG^FZfWd3<zIw}jN&Nlv%N*$Y?$_!g zJy$Si9B|I6(nIQDQ%C}~@MN^xZ2ZYt078~!nU3S=L2p@88`40u0zq;`jbcFE_6FYm zV891*@}5-J@UsR0)*l|b>Zo()R91p}9ms^moCBf4029Lmk<CCIF-4KiO}n>w;;r9T z*QsbmyR3x^wNy=Ls^w|M-P;H7MuNv4uQ6J2pkC#Qe&EyzSAbsvdp#C$65yQ58@T#4 zvcaea3CC%wfAbOmcLd@w--fw?5Tuqvk^x)C`vDyG6L5%8QeU0zsI$bD_dzB>NHRFZ zKnCC}gR>xH0M1lj<p2?v@x<d_JzC3IT@AdTEp!7Znhvr&K=AhKFh@!HNK@fDgHo;F z9IC2YAaDtQSCv5aHO~O3NI)5O*l5rUx2LJ1+=DvuXh_g#Onx-O7z;osk+xq4S3v9# z*k!<Fz|)2cz+q<B13b*0_pR+pdMN-{@crHf-l+iUs%&lgYx|38PLkEUI5<BB5h2u@ zh`f&B7609Z4`cEeO#uC%6K9|E-L*Rtb(<NcAgVe}sya@p4g(-lGMTvg`plhrR)Tlf zAw~%}XhyyQpgexc^SAaUqC3E0B7`I(B*B&hha8YiOkuI`_V?bfqdQd6LKnG*Aou^> zK>=>96~NH|W~^Mf@`_EHHa-gAi}(N6NlON{PbkP_D|4ciy~KEa<<C<##GAxT06r_F z<U}Iza{wKRb!pTX5i!;Qqys!R7(|Bw*iy>BZr!@|fuoN;dbuJj!xw+*Z)0|d%zJ+M zOcsD^nkc$1x|b|jg3iv)Ulj_4A4j9n8w26YVGB<VHh{b2zmq3VwzqHJPLAU&W#*<( zDD+v&vL-r?)2Jo?{^64VNdP^|moE>WefHT;xvu*_C=|LZkid^=5a|HU(W_?$0FlXL zE^26KxF;5i9iPkP8WjZ<#ZWH^0z6wmk(9a4&Q4mgWXU?u^Zsa>=ErJ)$YT*iu^%dn z0Ou!~nwpGTZn-5Ei^cBg>+8ELm&<typd^4U0NVg;2e4I*Z2-E}|BC=<>C&an{Q2{z z3L(yQUH9PtJr-;l+Ff_nn3O=O;W%_6QUGArbr%DOo2L08Webgf`stw#YfQAzVnA>1 z+_|)D*|N=2$|p_Jydc2I$r_=~NHUbes^WtIvI=;^%In6IipKl;`aa6c{P^RKpP)SP zF#wJNFabcjawKsznzYf3YDH|K`hEw1qvp?_-@(l6y6&Hp38+&%psnsBIBH4hVV6MN zQ3NS^BYfPfFcF!>^)HtD`uffbhr`oivDhz^R5}4xg#(hq*tJ_!Z(QUv&Nu_lJ@?$( zp65NQq&mnFV>fBufzVl_24E08(`;xq-E30)@U^~qN3_OoE=Y=%JP2ml_4d}S*L`dD zvFjsEEs<h6;bE|`Zm_`ZVb{V=uzq4`gsA}lGiT1knl)?Q_B`(e1tMx>EF3TmI(u`_ zL8K9|%%%}-w7q;r{Bm>t2G<N<;<DITmf#9AS0r1GM9YM7;<nG7)c&BlVc*0007{I! zuoKd9W5nZe^!N9#bzS#K+qOS{%hHvTrkwEpg_a?X5CpI6@wUHgSa-v9^Ji=ikgJCT z#61D1x5L`E{dwc5_M|DlDoW8;@IXZgb|e#nV3Oo8$>zw$7e4Wg3ubRt5?BaGU~nR} zS~~RK^oM9^Yr}hwKVDpP=R+Sxef%cF5Pi%f2@FHP6yUaEqW_CueE+z`(3&6jk=h^C zFh*CeFo3%F#3QcGxJdL?fbI&gvjX%wgroz`dV)$!`KGBeKC5|V>?Lkv>{|H>cEK|M zk?8BQ=70H?W7Fa0g-Oro=_&y`N)S8BL_H<)k`=L&np%Ib@S&%U!B7jXZ%+>?p&E?y z{320ZvO=vXhD<uZfD4pmb;cF}E`x2NLcg$2Re-wz&p1*tLJzd{-?ySuPQ0w!MWHBR z6lIlR6aXv%iW%T0OJqA{oN^6-YcW*Wa|0aXA!DR<me~dn$vdtnAcO%Aly`v=gP0k% zsJ0@O1e?+=_bUKP+SA{c^&L_>$G4jX06Wd0X?YLjw96=ZfWrV0Q;2}F0g}uzLg)Zc zM<JWJQr61?e6WBNBMf6ABHO>AP%O1No`h57mDyv!16VRs#c{@k65Chl4q!jxv7k5g z3&|nJlV(YRiyk5G0XYxIyTm!S`o7=+dB-IH5itAlB8@WVxlaHNh8z^K&6^j@{4G^0 z;})|V)Nx2jvM|sh%Ejz;3;t%gAII&7;pW}gM*?7(+1}S3Pu6>2IMs*84g)TTE0R$x zl^ZFW+ybDEor*YA0rl<xl-jH0N?CdXDX-z_ML#fdDcf*L6HStqC8T9}rAc9@kh|}^ zDYtT^#2NwM)(Cn>3M2<YAHu5NFM;RUELjpuQs@v@-y00jO$$!`9fsG94|--|jDT&a z+CgDNURt)Kyy4#Oy>#UGaY;j<k-E2J!>>N^>?5C<`v_Nxe%owHy%sRMA9*CwsGAL; zg);TeKC#p~WBRc>b8S=p)^nXkGR>TTBvC0&u-uZ>_{81cS@Xo=ZdBVm3K-r^#RrSs zHujSL4y9Vm1^|9(_3C+(d7$r(OFq2fuZtIL+W7R+?PS>nNH2+Mhg4Ecq7u0IeT_>E zEeDSPh^+qOqvg$u7p<N8o~bZG;c=Ahhgko@%S%6f)_s?L<lNknB};#O<dH`%TD*91 zLdBxq!LGCHDUdeQ)2~CdFRuYW*md3K*Q{Cdqgk_NtyPmNE2;AV%tC8{-4A<6S{7t0 z(xMf>H~`~7A`X^Ob#G9=GXP*`B9XYXxw-j6%a<=dY3|&)+pufP$EXvt<_PSO&+YMp z4?Y++3`5MCHR}-tsK8KrnGakp5D4#27Qlhu&Zv83u&kikBj~T&^&5*yEiEkz3xz^% z&YU@GUwiGfX)|Wb=n4e&813|qMnKp5wGNTVWd5zLuI^)&Wqm@?Os=~5oMM(s*tMr+ ze;a)CjxT?4bOUzn5o8QCB^3Z<i^bwM!r}0zH*VZG^|<4X>(U+KXiK6w0#zFHh|y@Y zvs5ZAQDFHgS5e(~a7n@b-k=`zx`D{RQNL{mQZ*lR&$28l5{WD<7K?J~)Tx`+uU|j) z_~Vc7su6LFiX@JtH-@6oDCP5cCZeM=napa<yZW?MFnK@FryB&3wG^+2;mcZw@rsV) zm;geNNaW^xKA)d9ZQ7>Z-rlp@+uK(k3=qu`s1e%R+iA|6IT0dio<D#73tG-vt|57& zWnc7b=nY2>hBvV5A<tlsA^<xYjeb9q$#l21wLP6mrOu1T<If!|Nvy@uS;_-90ch#! z>gr@>1~3W0kvOzPaN12hMlUnOROw(6fMWq1r$$FAmAYC=$?0@@0fv?}L^M+%_P1EI zCpJcp=((k(rPFoYXEC&7>QI&&gJb~Lu+z2f!wQ8$;dng$P%4#@jg5^<Q>oO4;_>)1 z`yh$5r-GF^!Y2;HFivtDXVq@%5Qm}$QHzY-YkOOzQt?52B$Y~urlzJP>2&)1rlzK+ zHB*4V1KM4e*e;BY*b8_*%`ObXIIB=7tQ{lO!deedw~>0UwNt577=Rs*#~(_k(^oY# zG(46{rRHfuq%+_ahA-7HYgGMe0$6LU$KXkYLSf+>Z@jTWn?S8(oendgI*4T*MD;ye zN#f;AO-+xa)9G^oh=y2HFKAhT0+(6C>krO(w|lEJ#t_O3RG-GyH^2S?OTb`)_ww4( zh7VtGN4hQoStt$$&Ig)#91LfpGunPK5DJAtRH;<TU0q#gPM9!ZQ7)IeDi({~2Y~u_ z-0=4e4Q&fR!jzsfkQ?Y*+&cMNzYhf9I@2^vdInWifoBf%edo&hNYB^6DKO_j5MXLR zMV|CfxbaJ0z))AcbAZ{$p#m`iz_KjMa2%(yWy_Y6J<m(^@3=dC%E@?2fWv^S8aonT zSdbj;S-C24-WhX#unoH|{o-0b76M?FGPm4ki|ywD8HZ)sBoqlOm_>s}qTTfPi0uvI zu0N$v7*rQ`Kj^DN4Pr!vW?}We%9FppX2RSvcRb1PJYr7(9<je112%)gL@@EgpT2qA zmDes$1_Z_pItv>>DBW}8m5p_qt_2DVnE`W_KxHNvV8n<-D?sDnV#+I4?3x9d%^B$u ztHT9i9fn#(LIB7~U%1?)Yy)$N0EY=Km_Z<u0h=J|m|#=IiLAF<emf6CtEegfT!9+B zFAb6BuLbX{Kz0+jm%s@?CJ8bL$bNu(nR%ycV$1tqU-Q*j+7mVdp}|=2)VkgBmxXx% zXzl8SXPU@1vP??iJ`nd1<W7R@QSZ9}uZtjiT+7&Y{IgGfWjclz{)MSMp%4K3<rglQ zLvk$u$7C)7$TPS^fX5&MU<WK2B!mfAm^87Y6Tll9!Qg3XJXS&;orxK2GiizEPXx*U z=M6@dff9&4fSFhd23Z6y0vt97CYoa}odIBr#sGO{K*Yl&Hyi_`0d|PE1m-dUPIWgQ zDM0W5DTv8pAVT2Yqm*sYR~XU3EG6zo1_RVs_Af-f%?}$1Wr8dZ;!7Z6=>X({sZ0bD zg#y)MXy+mefFrcpM+iXVt*wO|WC6?tX4dIF4<MHr4nRdF&J%GSg@Th)M){DH5)UAY z8CL1*Pt<Lz?*HL2vy1BPMIJ!v6)wPKkSt4Z37a<;vRV#Z-7Yjx#IgVN3$L#OIUsV~ zVJ*rm0hZOh^`u8&*8?1Eeyj0Sby0DcgXw*-iv+;>_$O~$Ne}>$vrBf!>XjHE=>Zb| z_%B=6>fq^_h))oR{v^ce*P_e7Mu;Flg9StYMAaur1duTDMKd*f-ogz5$H8M9yutRj z^_x(9ZC(2xz*dN0GLZn00GQOg1;8dVEbHN=jlaQA&3kZJt-h7fU~qWKoNW*Er|Nb> zga8;0AQ%t~CL$Of!-6M6@`pd4aVv(}F!c6YIY!mjpSbUh-S?gT*h3rs()=o56JU8@ z!vzeFVK^X*K{mbgO5=+ky?EPG+PTBF0F@eD?&ptA+ms#Od3kO9YfK>vFo@VtOSCMb zw?Eu}+Bw;8UU$RYDJ^j?kD-)qcZa^3ssNy9Stw6Gb=9J~?i>GLp=7%t%LGh=U|HD# zJAd1q<9>76ytn_`vIdJ+Is-5D;(0`or){YAHn#z2T5xX1#B=AQPB?N>xw$`K<z86T z_?Jb$+4h#I!1V&yiRyMEDOH)PVAqb+!+ivIh4bgys?9YHAiCh3BU)q8irv*~7M^>4 zM_LukQmF1n(~Y6si;AeOA|JlmTwSP5jlsQI)c^f5V_v)HP7G}|qVE(m)+CSumA-m4 zw>DH8;p2+c4h5=xSv6OGQ=cq`O1G|AqhMob7jU(@tEH7O{fqjefT2!+vAj&xpDU?* zCXHS9cmMF0X!xHEX!9Jv@Rk)eHQV4D$ejAj(JtWEJtT&2GvZ&cgyFq-W7P|jW(P@W z5^FZGPD@FBrpe!cOfTR1%u`K1a8KJINetf(sf<I|k37n%OaT%ltdXgH2Bp`&YnE<l z5A4P6wnOqX*2N3tmt!5gS|A#l;OI-sbP_eVrOIx%e%jaVkjCsHz<uz{!2%lXhKQ(H aJo`V`RkX|Juz#xn0000<MNUMnLSTaVoY_kN literal 0 HcmV?d00001 diff --git a/vtm-app/assets/info.xml b/vtm-app/assets/info.xml new file mode 100644 index 00000000..52b9a25b --- /dev/null +++ b/vtm-app/assets/info.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>About this software</title> + <meta http-equiv="Content-Style-Type" content="text/css" /> + </head> + <body style="padding: 0.5em;"> + <p style="text-align: center;"><img src="file:///android_asset/globe2.png" style="width: 72px; height: 72px;" /><br />Version 0.6.0</p> + <p> + <li><a href="http://www.opensciencemap.org">OpenScienceMap</a> is + distributed under the <a href="http://www.gnu.org/licenses/lgpl.html">LGPL3</a>. + Please report any bugs, problems or feature requests via our + <a href="https://github.com/opensciencemap/vtm/issues">issue tracker</a></li></p> + <p></p> + <p></p> + + <p> <li>Map data for 'OpenScienceMap' source © <a href="http://www.openstreetmap.org/">OpenStreetMap</a> contributors, + licensed under the Open Data Commons Open Database License (ODbL).</li></p> + + <p> <li> This software contains fragments of <a href="https://code.google.com/p/mapsforge">mapsforge</a> + library and <a href="http://code.google.com/p/osmdroid">osmdroid</a>.</li></p> + + </body> +</html> \ No newline at end of file diff --git a/vtm-app/build.gradle b/vtm-app/build.gradle new file mode 100644 index 00000000..6924ba19 --- /dev/null +++ b/vtm-app/build.gradle @@ -0,0 +1,47 @@ +apply plugin: 'com.android.application' + +dependencies { + compile project(':vtm-android') + compile project(':vtm-themes') + compile project(':vtm-extras') + compile 'com.noveogroup.android:android-logger:1.3.6' + compile 'com.squareup.okhttp:okhttp:2.6.0' +} + +android { + compileSdkVersion androidCompileSdk() + buildToolsVersion "$androidBuildVersionTools" + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + + defaultConfig { + versionCode versionCode() + versionName versionName() + minSdkVersion androidMinSdk() + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src', 'assets'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + file("${rootDir}/vtm-android/natives").eachDir() { dir -> + jniLibs.srcDirs += "${dir.path}/lib" + } + } + debug.setRoot('build-types/debug') + release.setRoot('build-types/release') + } + + lintOptions { abortOnError false } + + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + } +} diff --git a/vtm-app/project.properties b/vtm-app/project.properties new file mode 100644 index 00000000..916037e3 --- /dev/null +++ b/vtm-app/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-23 diff --git a/vtm-app/res/anim/slide_left.xml b/vtm-app/res/anim/slide_left.xml new file mode 100644 index 00000000..315ba385 --- /dev/null +++ b/vtm-app/res/anim/slide_left.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/accelerate_interpolator"> + <translate + android:fromXDelta="-100%p" + android:toXDelta="0" + android:duration="150" /> +</set> \ No newline at end of file diff --git a/vtm-app/res/anim/slide_left2.xml b/vtm-app/res/anim/slide_left2.xml new file mode 100644 index 00000000..1a876727 --- /dev/null +++ b/vtm-app/res/anim/slide_left2.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/accelerate_interpolator"> + <translate + android:fromXDelta="0" + android:toXDelta="-100%p" + android:duration="150" /> +</set> \ No newline at end of file diff --git a/vtm-app/res/anim/slide_right.xml b/vtm-app/res/anim/slide_right.xml new file mode 100644 index 00000000..bf46b652 --- /dev/null +++ b/vtm-app/res/anim/slide_right.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/accelerate_interpolator"> + <translate + android:fromXDelta="100%p" + android:toXDelta="0" + android:duration="150" /> +</set> \ No newline at end of file diff --git a/vtm-app/res/anim/slide_right2.xml b/vtm-app/res/anim/slide_right2.xml new file mode 100644 index 00000000..8590c3b7 --- /dev/null +++ b/vtm-app/res/anim/slide_right2.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/accelerate_interpolator"> + <translate + android:fromXDelta="0" + android:toXDelta="100%p" + android:duration="150" /> +</set> \ No newline at end of file diff --git a/vtm-app/res/drawable-hdpi/ic_action_search.png b/vtm-app/res/drawable-hdpi/ic_action_search.png new file mode 100644 index 0000000000000000000000000000000000000000..67de12decbd60613f6716de113daae46dc7d6ff9 GIT binary patch literal 3120 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 literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-hdpi/ic_launcher.png b/vtm-app/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..6a0db8b6e1f15ee592bc80ce418ee04453eedf9a GIT binary patch literal 3437 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 P00000NkvXXu0mjfEMan- literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-hdpi/ic_layers.png b/vtm-app/res/drawable-hdpi/ic_layers.png new file mode 100644 index 0000000000000000000000000000000000000000..a957474eda1c16dbbcf343331a2e276fd8841544 GIT binary patch literal 3340 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 Lo_1)mM_>C7p+`1d literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/bonuspack_bubble.9.png b/vtm-app/res/drawable-mdpi/bonuspack_bubble.9.png new file mode 100644 index 0000000000000000000000000000000000000000..31a2ba65da355e3e5f6b5fb7d727fc67d7ca3708 GIT binary patch literal 741 zcmV<B0vi2^P)<h;3K|Lk000e1NJLTq003A3002M;1^@s6o)ECY00001b5ch_0Itp) z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iyu9 z79<_+#q4$f00L!6L_t(|+U=Y@j@nQZh2IY@f)u$=5h*JmlN}gVkXUR0kp?NsRQxnl zlr)neRdx`I*$Pz#O+y+xAUBvuPUIiB(*Q!w1QD=@=gBz|vLzbs$ML!MJzHk%c+vXv zl$3f+e0_Yoa}K4ao5BXP@B3eb5E7tMxD1HmVzKz1F~&B4nGiw>Aqv2Ax>l>z_FOy= z7f^~4Kmfo14E?>|0Wcg4zz_ff@P7fAfFr%>0S-n)05Al=01Uv;>3}hw!NK_kj5`@{ zFaSdUd>-xIrhkf0>YPh|ZuSrnBLZJUM2;h3>tyJ}eAr<K2O}Z?7{ZvQz?cSQ91(=^ z0S-o!gZ4K$A|lSQJ)FWqgRqi2Lz4py%)$AK2sAlN5tt&NJqwLOGB?odvK!OT<g#2U z!@0=dJVpdZ20vDlOMU^J<lqPAU~CCz>EJkyLn2~l&R{O3EU@FB_@w1>89(-wm~>_w z$Kkf1_3@nwe|w|Rc-8CmK4_ZujB_sjvB*^vMZde<?)6|W_^ybPZ<HDtbX~8Srum}Z z?_X+~R^$g>PDDge6#Z&8n^(i(@W*^U|Efw&QYFAa5Zq2Clb5z_f2&j~Zw`5f&<1X+ z)%s*vmg9MzrBY2w0Zu@^H4NiIN~!JR8w7#hZnv*3%W@Wrg{4|O$^qVm>^RQYFpSqy zN-dcdL_}d2`khYav#M~F1)PLjE|)LXka?cq=0duzS8KJ}N7r@lJkPT|&$C_Ey{p&j zm%6T3K`2GY)9G|Po6W{~g3I#+xl}4W7eesSX!H}YkVV6g(v(t~dHT(VIvAT2M>4zz X(y%=X!_SJ600000NkvXXu0mjfB6C6- literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/btn_snap_normal.png b/vtm-app/res/drawable-mdpi/btn_snap_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..547ccb3f694931115d5201448363734896c51f69 GIT binary patch literal 1755 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 literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/btn_snap_pressed.png b/vtm-app/res/drawable-mdpi/btn_snap_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..065dac9316a13c8e0c1309b4e0f110413c5a17d8 GIT binary patch literal 1897 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 literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/btn_snap_selected.png b/vtm-app/res/drawable-mdpi/btn_snap_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..551e74fe2bf1f0af5e38a3d97c283e2c2b99820f GIT binary patch literal 1648 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 literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/car.png b/vtm-app/res/drawable-mdpi/car.png new file mode 100644 index 0000000000000000000000000000000000000000..4ff280ae67413491984b51f83d6709c5db44ca9e GIT binary patch literal 540 zcmV+%0^|LOP)<h;3K|Lk000e1NJLTq001BW001Be0ssI2{21+{0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzzDYzuRCwC#md%lZKoEs>%R6MC8E6Jb z1egJ4AQ>2fA!r7gffxZA0T=-q0sED0jpGcAh`Tr6ff}2h{_lsq-WOkdihdiV@pwF) zPJQ3kg=v}|4u?<u2SJc!S=E5;cI$awck8Rw3M*w<Mp1+(^3dO6dc9ubzN3%jaw%cP zaqLjbZg|@V!HV@f&)u5C;gA&f`~7<kxF8M|i$%*^+h{(5Qf=h?QlI*iH`EsD#}>_I zGdUDRaXz14I=Ei1yWNf<PLkxk`Y?qplV;3h-dx?LvC6c7;<j&F)5zUR#Eawc$hvl3 z8WvkGZ%vn~uCVX28Bf_WVbwo?JvY1h=YU`awFSLgF0_QhWHK>G)BtKIbExg#T}Uo^ zL5-bkDj<a>Kz%(KYeVPW2}VeXm}sF%aR#CCQ&&)NAXk$OWyydVJ|0smdcs*oOJv=Q zuL3uP=5!&!=`{1ivYfC>Mv6yua80pylSC}4P_QOEHc)&Lh9RQSXk_;a;byauRT98a z^xszFIiJsYo=zthHQMF2HSNYc2Hy?irsIpIj~{)mjicZ6NsvsbjGM7>Pi0sIG^z9V eZ(0ZZ3NQfO6co)_zipcU0000<MNUMnLSTY#$MKy2 literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/delete.png b/vtm-app/res/drawable-mdpi/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..7fca7db24fb72925a899978d8c816587b9624269 GIT binary patch literal 2645 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 D62Bqm literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/delete_pressed.png b/vtm-app/res/drawable-mdpi/delete_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..bc2d71f7bcd9ca98a71426c754cdeb3fa30fa995 GIT binary patch literal 2574 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 literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/file_picker_back.png b/vtm-app/res/drawable-mdpi/file_picker_back.png new file mode 100644 index 0000000000000000000000000000000000000000..671901539449fd2f4f4993acfdd4b85de89cf0bb GIT binary patch literal 1951 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 literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/file_picker_file.png b/vtm-app/res/drawable-mdpi/file_picker_file.png new file mode 100644 index 0000000000000000000000000000000000000000..d0411244893b084e7ffe4aae7c28e679f3d75603 GIT binary patch literal 1024 zcmV+b1poVqP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00001b5ch_0Itp) z=>Px&x=BPqRA}DqS-WoAFcdu$ZOefa#i=bIaDrsYFLddazv$kzpO7iPkfCe-pj+qu zMW&2_10w-aBrx<KZ-<HoMU%2*SwRO~zz`)d(mA~6KEwdM_10T&y|pk_XVUHM?dPMT zqhC=Jg`Veuv44L?M37QKN(muE>A3Bh8tdo9HSgi!;m6(G-S;}!26ZN7S@!Mf>grP% zh9DvU0OuS+2r$M#MDRRseNQ+2nwsk}B0>~JxVgFc3SfT(q?8~cyu7?XN(lh)eIHqt zt$``8sG~Wyo!f{8K~P&6wF#()Gse~{tw7trSCMdPd5d>idq4-H2q;^vRuDooUUg+* z7?u{IQv&Add!Dx;qix{z>-zJWc&rftwYn~OyuH04Ns>z7E?`82FbthZU~e&?%f|G= zY&I+1cg28<1n0aI15JoD@_;EJW$*9r$g<2SAv0!73C(7+?qk3PT*ZJ80(qW0Gto8) znK9If2lR*PFC`*Or&DJtZAwH>8ha-|M0k9B#M#+d3DEQN^E!yDvWW})>FMdmWuZ8Z zF&GR=Y1H?9aL#wG#AboqyM&ZWIp^S<BTZ9`$K$o@4-XHm`FtB01yt1%o1FF@17^DP zJP%J#Pe_tvLk3mZN4N9y^Ad?p2)L>-08AzmYc{Fd8!7p9O2E}{DL_)njg_|nsR~j> zzEcUA{SN@VzP=(&(+znM5sr_KF`Z6prBHS3B#l;iU_PIhq+=44QkFDsYv!6tr<Div z7*O>=2dB$P5vnR|datEEQ#&CbrNm;fz<fS0fm<vV)+|(~G6TTH#l=P{?W~YZCKC*Y z!_xhelN0OLnQLsy(dNlcJis|e5Clk)1mp2|{o>_vi8M`XdB9f4R2|r*Bi*T)GXruo z8g2bXu4=XQ80lO>wylyN2%LqiN?PW}(T8LqTMl0?msqV<8`3dBo2`Yi%gf7BHE!aK z)&x{wM^WUIkX{$UFvP*ZK`C(aJa>}oL_)T1Rn4*OQs{L-pSx@3X-pu`bM7iGj4?z} zWGz@G8CRF;Xek(gt?af{4+bDg)6}oCbEm$Y^^ISBrPY1cSbH7=Fa<DbOJE2f$g*tc z+VZG#ZWb(awas3Lh&%uxfS|2}IDml=f^*I(j$@_(nWf*=ptacc9~AFzMIZx^2qAu6 zUtfQT<9I+stV+u*!4m-7-{1cRkQBex|KRWeM1?#3f^@6pt-z8OzZHNt0Dl0;wggnH ujEb1xbzHlnWdH<#v>5l+TW`Jn=k^ydg}8-Qpwv|W0000<MNUMnLSTZDN7+08 literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/file_picker_folder.png b/vtm-app/res/drawable-mdpi/file_picker_folder.png new file mode 100644 index 0000000000000000000000000000000000000000..c2c3f5fbd80d71cbef2fae3f670e983fca43526a GIT binary patch literal 1460 zcmV;l1xxygP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00001b5ch_0Itp) z=>Px)Zb?KzRA}Dqn7@x4SrNxS)vtfdjQ4yxu|_LFLI64m5kW$Hk|W1R2u>tiWSp}J znG+mf$pMx)>5w>yf54FmAaDi_5F8h*4s5h{_WA7d?0BcU-_IhLA3NTio!y<?5kgF9 zYTov|p5FRa)vNl}z*^R_mbI*9E&szJi#E5nw?F9hdOs17BloE)BJ$b_kWxzf`}@DV zb?esle;vThn>Y8aUcGvGFc^?hnyRW!*F&ls8yoWA!Gm^hZ}08fw{JgQHGq84rm8k6 zrOUg!yHlfMjHoIx#@E1%@0UE!+1lDFo<D#7^&2;CJic@1&PoAfi#9_D?IcAGr_a1p z0<hN7>2wV6?Nu9pVgy16P20BAbxn+M>h;4qn??X_+tTTDes%r&^><QA<}m1ajsR*A z*}i-C?xz<Cpf$$O>-F&79}8mkOpFcFD_5@cHa9nau(h>CQ51*>B0`pB({-XE!qcZu zxqtuuXTZ-d6hK|q^!t6BbJH_$iYN#`zu#wPXJ=F`#sDV98)Fa=j4@<cHhIQd7kUP~ z_l<~9mL)||V2mNp^XWgGT@N(Fn2~4mJf|$nu@;nNNmW(T=S}visv^&GVvHN}?D*b$ zA1)sO=N!&CthMZXS+d(VsOF%GqG%eeqnD1HKbzY!c~b;I1eipFgQ$R^@8ACZw|?~7 zI~l2%e7!1-0KWh}UkX470c$PJId)!N=Di<YWo+%iVSMcmMdy~}IiDT5&~GUGYzDmk z@z0OsZ#z%^1pNNY0KE4NKvh+Q7|Be@SKsRK*GDfIm;O_=ML9z|l^MWad7~6T5s^nr zr_gXXWH1=8zdxiXzD88#RTEZP+f_$VtO}#n2LT>01;AR{<ath~)4_*GMVFM6q)H~T zBI8FDtFe(P&#*4?@cnl`TdE4Bl-j0gXxnzwoT>`v6LXXNqR5#*Op5i9s>sC0w6GE_ zj6jUB@!n6s(CK!uKAs|}mrtFfP}?}FKt)Cf@e6>z{N~5YMZh^nZVXW>dfkfF$CVg9 zDMYo6)9b1*cppDsx)Z0An!2v3>zaP8Y+fp9T)Nl*jLBe#iN>WVIm|LJ8CV~mor_AS zDwi(xNJF95DJ~=evkW&WI3IB#O(_RJo@J;)W5e<QRJCcE22y0uUZThijf<zOB#REJ z5R>A3A|z!VWzUV!xOhGQF-GgXr&H!Q>0*rF7DQk~=0;wf9LPycp8y-}r)g(Cpb{Jb z@a0~EYFZA!wQJXCn#OtWwOi%Vrh-f|d`z!<R}~)>9}_Vh%k}3*VUd_D<HcSB@N6kI zdhgynQcA&Ei?fzCRj7<oLjfNWwT%p#NaG?Q>JbAM>1p~w|CL3M{&#=+yQNdehY;v? zyVz6_6>1l!JZR$O(-@@3aiU{31lZeelL*7}9O7WD)xpq;3fQ28Fni$S5YyC!O<Iq; zo<j^ng2B+Li0nUn|J|kgfR8aEMHc`UM{Qouz$Nrx95|7J%=}b<kBL`})$>HaSZfRK zz1jeIZm{FBcR1#HoQvn0#;H(Puhg~E=Ec7hFhD#DfDG7Znx+#%z{ZTSET~-~Mm;fA zj?O7(xOXg#Pgv`<e)4e+Yyg9KPdW}j0d&3hc}fXPMNt;`5N8INb5K&{#E3W_)rXM1 zdh(A5bb%JIrv)H@q!_*Pk=^=D-rU^4xz!Fbvt{YU^Oq7`{c!N<-(ODbIZFz8!2bK6 zpM13O&3AqspV{x2FFrz5WyQ89Ac+X=u>bKVf4Kd@IQQGJO=lWc3q;;PwF1g<S{YV0 z4NUDxV~v0VhGTWh%-b?Hz8ps@J86cvpd1P_030iw0oSsYwXEg;TK)t6?!Wl)D7R_= O0000<MNUMnLSTYH<ipYc literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/ic_action_search.png b/vtm-app/res/drawable-mdpi/ic_action_search.png new file mode 100644 index 0000000000000000000000000000000000000000..134d5490bd3310559cc944d44d24857eef29f2a1 GIT binary patch literal 3030 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 E59N2f#Q*>R literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/ic_continue.png b/vtm-app/res/drawable-mdpi/ic_continue.png new file mode 100644 index 0000000000000000000000000000000000000000..50a54e0b941a21dfd284ce12108f62fef3ec5bd2 GIT binary patch literal 414 zcmeAS@N?(olHy`uVBq!ia0vp^jv&mz1|<8wpL7LMEa{HEK*5du+<y{Tfqc#akH}&M z25un`X1sK_?hjCqy~NYkmHiGg2dB6R!>#;u1_nl5PZ!6Kid%2*?B#7X5NJ!3QaiyS zD5|<SWkLs|fN(%6SKA4WmoID>g#%byC&wsEY5DWO_`&xj{xO9LKQrGPQhN2<PPS;{ zc?IE{%Q~F&1RNL`Ted2c#EL(!jPqQ=&*vyMSEW<()^3-od;vRwWeoQew+O~DR<vGl znZf*lZDH#N%?5D}-j;mED~d77f4CNKJrsHv&tS?C&f(3t!eNI)gwz4c7H$@ChU5-k z1&QQ7-^)wRPfER+o43&D^Q+pQ-=6vRq(+JrbCjj+Kb{7*z3^&jEtj0*HcJqXg#clr z7!q2_CwK5%!-njZZMpmhl|A<Ee*WUgyhl=kVc`l542MJ>Y6&cK6L>V?|6GMih8$Z( UhiSShpqOCrboFyt=akR{0I9Nv6aWAK literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/ic_empty.png b/vtm-app/res/drawable-mdpi/ic_empty.png new file mode 100644 index 0000000000000000000000000000000000000000..7e162eb62f625bd4e5f246c8be66a28aa4ff630c GIT binary patch literal 240 zcmeAS@N?(olHy`uVBq!ia0vp^jv&mz1|<8wpL7LMEa{HEK*5du+<y{Tfqc#akH}&M z25un`X1sK_?hjCqy~NYkmHiGg2d4s4e0tO=pisT1i(^Q|t+#gqc@G$HI5^g6EmK;a zRF&16qcS7IVv_g|J4TnZcl)=0Ke4D^U5%fGLqNfyfq{{U0>%#MhayL=a3%Wwn;TGj YXR^?al`|%M0y>4k)78&qol`;+08Jk~lmGw# literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/ic_launcher.png b/vtm-app/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..bf571d67b031816be3218d94678db83c3152a691 GIT binary patch literal 1396 zcmV-)1&jKLP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm000F$Nkl<Zc-rk* zYitx%7`<(`lu{nnDl|SKDo6raBKv4fi-<-A<0D{d6)`p_B32PBqQRu_r-|{gNU(^) z5>f#b^@qj)CL|=?nF0x-##9p9&??4MI@3UDN~N7So;y3cZI@Qkr8_%BCONsg^SJk% z`+fJj_q$h!E7z54$P0}k3%V$UCTfU}hy^$)vM&$ve~B?9ztPmhSrRu*30-vXUhuLI zL84EPa3_hwf*vUm5mA}~Mo2GEF)?ridHkK$cYDC^(ZmhJb<onc%U&m0L}81O_da%% zyCm*YvCgmJfM3BruZk4~-y?@a#}hXb&lrZWk>yU}3!==>GD0?43sFlTa&L>1J)h=R zjrV+t*%DCA#(;`OziR$LG0RDGC5eB)av3p;7!&)fO*}=yT`?EZVlH$R?czzt_!k|* zWWQo6?D-~&Xp#bC>95DPHO<H;Y0sd#IZ%x@u^~=GCk4DCqmyF-9{mAR`U~*c0(=U- zDfk&V|7kpmAmK8)%_a-Fcw9y&hdc*kiD!wWzA)C)WqzmYSQa2!_<ylG!yk}A6xjoE z6>-fSVdQ$lsASLYl|ApP$C7&vVze8{XA&<HUlP9f=Y@wcUPhnP6YUUr^v9L-%VR_f z5#s;pB)ytg>+Ts6B7<S9mNVnIev^zr5e?`nm#&lLZUMTuD>kw839!~GhSM`-f&!|{ zDCvFzEGx8$Ffq>E0@}qgXfXk?sh;R1_R+;M-7TP1Or!wKj1yo@^KIg3_q!0d2<w<# zE+Y^HoM&lJKr1ctI(q)RS59DVCZA5cNj!qn;uex#L;>Y2i|IP@*;~XqIUl%Hz(d6L zi%et{#D~N<w~?R3rEe|EsTX<AL#!Y+5Lda$b*kjzY#8rX5}}llF5QC+F2D+jMJ4rE zaJ&-_4#zEIsz@xM)poGVb(B0;iXN^SpCf)``u7{x^2cW>*m7qbt{tZ3q=;9#4OFnM zX+)6NChPVd#e}2@D6GSDuGbF{O_3DPDqFk?o=?F2(q<n<0XyMnG#`+;0TY{Qibzhh z^4H-$Z#^0$@qVBsj2(&8=T(e%0}5Jvs+EyhV1363e^LYl!kEb{>WEAOV*(VjS?(qz zRG(^u=r$+pl+sEqS@$orXi3ucaQtDsMB>d%zE4;t592dyPbU$JDkzgX5q-N7R;TP| zf-@;;;~;nv*{d2buSmsfB(ShpNql0OaS&q}&EB90jw|ygTH(9OUvWRj$MwmK{rL=m zL6+Ik{hdn<%j}35zB&{K4j_;9781LY7GhcPTw+a<CrH`G*Tjv<sJvP>;J^$&pO)Ur za>__n>|v!LmJ@^n7u$KjwnY9&Rd^Gz%i;CbZKi2T5i>`kvN^=tDOfa?5YnA)$E5@f zyQ0TZwDOhFX%BI0B6~hr%xmXD_f%PE7Z3B^D2D~e1>jU7i0;kPn6AD`>^IxQUS@w4 zI3uneihL$tSj(4n>tG>rCUwTpL^Ya)<NKI6w|+9TQJ<1WFZzy{ddP40pj$WW?fHDz zj=qBg(>@XDA{^gICdn7sZjY(Q9dKx(o6nXG_QCu1Y2ZKsx@C=BjtkI4t-bqa#tD#o zLW6y<slR{$(8X6y3ovx?iG8#>@RTwlpk^=u3*`}JoLXst^K`ElQm}<NkEGi#I3v^O z=5+T9Kaj^cn%IoU*=*GR|KT9siPIw2aRCe*Y<~^$w;ae6(@RdLtUxo=85vqd4%IC- zA|w5CzPvn@o3)IL9=1P+Sj~>D#3^yN>-S#&ch|q_k2o7-0iW^!0000<MNUMnLSTY% CAB&;@ literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/ic_layers.png b/vtm-app/res/drawable-mdpi/ic_layers.png new file mode 100644 index 0000000000000000000000000000000000000000..2e54374c9a2b496a2cb651792910e8a2dc2c39de GIT binary patch literal 1833 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 E1w(5%-2eap literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/ic_menu_mylocation.png b/vtm-app/res/drawable-mdpi/ic_menu_mylocation.png new file mode 100644 index 0000000000000000000000000000000000000000..9d1f4403a931ada4c33f5fab6123e5869c913804 GIT binary patch literal 1409 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%qp275hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8<fR&VF+bTgE72zA8;GAESs$i;Tpqp%9W~g9hqGxDg zU}<8hqhMrUXrOOsq;FuZYiM9)YHnp<r~m~@K--E^(yW49+@N*=dA3R!B_#z``ugSN z<$C4Ddih1^`i7R4mih)p`bI{&Koz>hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83 zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0<RzFwUtj!6b93RUi%Wu15$?rmaB)aw zL8^XGYH@yPQ8F;%(v(3~6<9eJr6!i-7lq{K=fFZSAS1sdzc?emK*2fKRL@YsH!(Rg z4<rKC;p=PVnO9trn3tUD>0+w{G(#^lGsVi;#m&XQ(Amh;$-vFf(ACh=#L&{!#L3yw z&D7A`#K{n**Cju>G&eP`1g19yq1PCvUQlAlEdbi=l3J8mmYU*Ll%J~r_Ow+dR=1d2 z7#ld@HV>*d1*==YdQEV;MIY!GeNfaQMKw$an0`P^c)|s8;7LC<518JIfC>Bek$Ykc z42+*WT^vIyZoQel+n>o%;F#rsQ{DVgCIY%VT)kpWN@80)lDBNxa5O49xx4p&!_>8N zxx%*HkWl`?W!KcXZHB}OfphOCy`Q9eZujGNPq=PAnf~VNxpREGt&O{vXmp)F{p@?? z9X39T(ggM=b0s*eN)qI1s<tvaKaeiDx&7)Axx)cWk{g)P4zdLB)-+TXaOr6j-))q* z!OHzWET$>E&$m&fqgi_cyPQsBsO$y@sXGly5loZ%`i^W=D!m+*6CGK@7IHvwg6@mc zSJoD+Te$D0;EKQ^mSYF3GJ@ooA`Onew=Q6l`ZMjIp>j&|^sCndQw|7waBZ@%U^o5X zku~Kg>t2!PX+75rxDU&$_;e_9e~rA$0rB!BC+k|06<<xWG=Hwbe<Z^r`_(r8=4pL- z-N})TVas?|PWRctD&%937$AP^it&31i#vkvTKkLDEBl!qNQs=0kziYF^2}}Ht4|$& zs;rkkcW^vWr79qL?8@zZSsNl6tfIP&yq~?j!`OOLNA<*&fa-^OFW=rVF5NQC=yXR~ zbj_jP3oI8N`)O9etlU#R^RLx$sY8!AZ*7R>-r{1ycWsY{OZ~jAYF#(gZ}r-4r>|^` z>A9nET<G-2MRSYZd~p<7_GYg<ztCRwn~amMsbzfs?R(?dp9Oc1-mMS~mn_;g+vG?7 ekN2`a1QQslowEJ!+xWMG3Pn#>KbLh*2~7ZF9Rj2P literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/ic_menu_options.png b/vtm-app/res/drawable-mdpi/ic_menu_options.png new file mode 100644 index 0000000000000000000000000000000000000000..d3e42edcb6db096d90d1b28a2d4609d3419c5751 GIT binary patch literal 1339 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%qp275hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8<fR&VF+bTgE72zA8;GAESs$i;Tpqp%9W~g9hqGxDg zU}<8hqhMrUXrOOsq;FuZYiM9)YHnp<r~m~@K--E^(yW49+@N*=dA3R!B_#z``ugSN z<$C4Ddih1^`i7R4mih)p`bI{&Koz>hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83 zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0<RzFwUtj!6b93RUi%Wu15$?rmaB)aw zL8^XGYH@yPQ8F;%(v(3~6<9eJr6!i-7lq{K=fFZSAS1sdzc?emK*2fKRL@YsH!(Rg z4<rKC;p=PVnO9trn3tUD>0+w{G(#^lGsVi)$k4*r&A`;u$-vFf(ACh=#L&{!#L3yw z&D7A`#K{n**Cju>G&eP`1g19yq1OzjUQlAlEdbi=l3J8mmYU*Ll%J~r_Ow+dZnqfY zG!Lpb1-DyFaO%|uIz}H9wMbD769T3m5EGtofgE_!Pt60S_ab1zzTW#wpMimKt*47) zNX4x;(`~&EJ4hU#eM=~Qh3+e*AEGP=FItrrTsY|WOZ1EJjvdw(oa_9oZXR?!=qWq3 zDc-Rl$fx$;g=0VJAIMucT~d2m6!_%<`}Cr{HsA03uk3vKyH4?Zpw1JUBJ%}HD)(!x zD6V6UWBYsX(wau*4TpZR-Y}|UE&5gYfy*j^@ysj}hnSn8^Y$O`h~hOlpeFH@&9n6n z+cyXQpRvy#C&-_<aKOKSNAEFDLF3`pOrDDVi2RRB`R+e1t~b{f^lK8E@#wVO0k#T* zrHqz^k(JYae=q3_5ZULo$}s!L)$a?O^6xad$5uM@zAxS&yLla(nC1SfQ#(|q`8WES zd(T<_@rYZN;}5;vB9Ura6C_QhUbbsoJb}^n*jK(89+K<y_EvsPUX>`sd!|u1&Gh3b z<B0Tq=8G8hx2*GV?lDynI-Ps0taanNFPw+jR=(WTl<-*f-1^@V6HMY$g1A+j=Uw!6 ze3;31sx9&x^F4#V4s*G-w`&}4Us&hBBf-2oA?l-7_jQTP2H)^yxgK7E_r6H#hGuDG zc)YQ{%r8><joH&}hkVBCH|%w1+fV*Kp84vd?g0jm$*nw(?nc>xiZf4FKbLh*2~7ZL CljEBJ literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/info.png b/vtm-app/res/drawable-mdpi/info.png new file mode 100644 index 0000000000000000000000000000000000000000..d7b7e698630e6b7b9235a937ae682db2ff94e851 GIT binary patch literal 1333 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%qp275hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8<fR&VF+bTgE72zA8;GAESs$i;Tpqp%9W~g9hqGxDg zU}<8hqhMrUXrOOsq;FuZYiM9)YHnp<r~m~@K--E^(yW49+@N*=dA3R!B_#z``ugSN z<$C4Ddih1^`i7R4mih)p`bI{&Koz>hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83 zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0<RzFwUtj!6b93RUi%Wu15$?rmaB)aw zL8^XGYH@yPQ8F;%(v(3~6<9eJr6!i-7lq{K=fFZSAS1sdzc?emK*2fKRL@YsH!(Rg z4<rKC;p=PVnO9trn3tUD>0+w{G(#^lGsVi)z|q{?!pPaw$-vFf(ACh=#L&{!#L3yw z&D7A`#K{n**Cju>G&eP`1g19yq1OecUQlAlEdbi=l3J8mmYU*Ll%J~r_Ow+dZnrq% zG!Lpb1-DzAaO%|uIz}H9wMbD769T3m5EGtofgE_!Pt60S_ab1ze&V@(9|Hs9a!(h> zkcwMxuG@Pbc93BEux#V3ATGV5T&+r5BIaybvAa2<tGT0&;iI=jXS};6pI+fKu1A+n zD(QB_>Xd!qkIH;3T*1V*^Ul(3a!!180#)Vj_p0oz7Cu~hx8>Y4pNzahrdz@@B$NHP zrX_NU{k$z~bHKL7SG!T?&0)PM72ogOoZhHdvVd>-hebkH0`9cEE@09w;GTY=CH<Rz z1Mln&T;&Bk=9f;yF$tMwI5KZL$Rn9u<kMW7wdu#Eqt_)~Fm-XA{M9w*a#GJmp%YEa z*8@0SY=1CeO~XH?=T<$UmW|9W+U^|VvI$as^08D>yFm8h`~tCa51GFuh!~{x)y7#j z`yaG?{55g|GxOEv6Svb+3s|~;KR-AjsL4?In1=DPPnHjO&30aO$(8G?sAXN>ROow5 ztFT&2bwa!Oi}tq(eIM7mu03D)vp%$^$|kN@I70b	j@~k{{1EsZVJ5yV%dgYXNWc z{u&?8V;Szg2?tg^6P>nd#e|R7(kf)Htm+Bmw_;8?>QN@LJhA!GWs9z@;*0GykKc;- v=;4r8i;6$F>|0RQk@;zbOuK}Ch$S$X>PoKli8&MkD#$!t{an^LB{Ts5qQ}~& literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/info_window.9.png b/vtm-app/res/drawable-mdpi/info_window.9.png new file mode 100644 index 0000000000000000000000000000000000000000..41503968581467a613d3f8b62c3c5ca956642edf GIT binary patch literal 416 zcmeAS@N?(olHy`uVBq!ia0vp^E<o(S!3HE3H(gu;q!^2X+?^QKos)S9<gg`qySp&_ z2f+n<mrn+Ya29w(7Bet#3xhBt!>l<HKtc8rPhVH|JKQ2XCS1X3DGPw6D0sR!hE&{o zJHwl=$w9zncAMz-%hnZI?+?7aF@f`Gn<9%q>jU+d;hlv-=I0jg+LgKa@ud$xm-_wZ zKl-6|<{ahKD~)Ds%YFYY&W%M`^J;0WvGHk#Gk3kq*DKvx^#1q9H`-5yqxhH_7*hQA zF|o8WuqZJ66cAv2rXb^V62xIVQQpA8W04}vG22LnNuWU{)U^6w=dHqN`sI^+7BL4K zNvj%LsmAQ}dDr8tA=z-D%s+E?hGqMP(&LIPul5B@U9zZSyWGrUtdn24tq9UTqj-9f zV#rIQl9{p1k*4bxFZeAWxN5yy`7J%kO<_i?lAm-!p4+n&pLiuBRH?eD#KUXl<P<@p zlz&fxy<+X2-;RH&pO$v5fkClF!0Cnmmv?j4X_VJ71Ws%cmAP)R8W?;Gp00i_>zopr E0BEY6<NyEw literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/moreinfo_arrow.png b/vtm-app/res/drawable-mdpi/moreinfo_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..04526cc451859ed60f507621763b28076f58e4f1 GIT binary patch literal 1500 zcmV<21ta>2P)<h;3K|Lk000e1NJLTq0012T0012b1^@s6R+DEB0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU%en~_@RCwCFmupO$WfaFxX-nJD0R_2s z6%bG{#$?=t8JP3KK48?aOE$A524k`qrkOEIlxc{XnJFL4qKU>VB*qO{G!w%Y65s<G z<!XoyWQ>B`*M?gu^nUYyUfx3AwiJwsPjYxKea`tk=XuZNK^6!E=>HxH{&Vx@&3~no zo10rQJv}{zTMK00t3H4J{0?-FiAhGI@zT=L(z(^u)kho-hkRjSfwHr+3A^OQ6mwi$ zTtD7_hTr?}?A@2AyTIZByVlv+c~LHxD>E}Qsi>%kv|24G6kBQ@k4Le&xv4jqOeY>Z zc<@4MYU&?$yZr(dFdS<KqC%^xs`7`2hr3`fmqF|myos2nR4PfQ(+N^oSf~WJw7<Xq zH?>;*rPJwrYu`|KsPF6R8}|8p2TMy!Nu$vS@9&%3)Ms?i+^UBb*0y#~uTzjAMM)I} z8hSJ{o(u*9FHi;j*A^ESRW6t7ZA^2-gaW7;8XEc?-|FCzBS%OklhN#ohi>;<1!(w~ zUiJv`%YC~5J@a%TJ)Ekfva&L5Z*T8efNfa(<#0X8o=r?lv@9<#XOxtb2*4Q_{G@%E z{+f4@*BjWDBz2tRnT6-@T-J$?kEiPDYF%Pt;%S&Ywks5qo|>7Nd69vv#2#GzX^GYy ze)9MN+ww|PGJS9|owAY@lJ_h;mvvZ|7Y&cI5p<0(uZjw_TCE=;GUGTXczpA-g{*eJ z#G98Z66x8Z1C*eW(Uq4TB0a$29<vVX@<>llSEi+<rQr7Y-Js8LAaFX|opaHjv(5-l zx<_ra>GTWp6I5~Z{)sfo)<t^Ex@?07qB;|oQxVYP$B!RIZfP?zGK81icQ+$EV3|ZP zUAb+h4H2vb_`_4_bVwKJG27sQXih_(AI5Ex1iHSyUcosROnRf)9kKeLOaT1OccvXM zg6is-3_6r5^_Xo4=>eOF5q4WIfuf2SkkH%i-(Pe^^)x6Gi*CC7t%+<Q;A2l{C3DzD zZ~$t<<&Y##y<V?8wBi`ej=ejD+)uucLT9mgchy5Av)dYpr+7JuI=VfnNUw?sd^}r4 zx8BI1M9tPhtw@zS0~X1gP#Y+T{9uwmZ8lpEehBOsRqqMZCCImdXRlAwy^!q;v*ANI z)Z)4zfublF&1SQAetw?Y6`d$g`cFU}Y=do0PEL;b{r&>HT9iOhbA9-(QzIiIgjz|@ zJeEM^1qY%SO5PWcb=d~nLf26v9ml}%M-&J2&QDiXR(vQD!t;Bz8FWx1ZD$|1ndx?~ zB_@z{SeFMH|14s~Ff}#xl_aou3lK;g#VI##+~`H=%B!iVks+aJWX?(FzL=p6hhJh? zuT!GEd7=VuUCf~;4Qj#5mX?-#8yg#r`uh4Ba7^Kz`Z!&?y1HInTU+y^y%D~Hpv*?X zw>lb-=LCVA39!|LYidVFM?HStlyL_L>!%#K&eqn}(X6bj*KvOxl|)lqTr3!AeKUu8 zC+ze?pM}O2oZH*VfmNhe)2WJNsw~`E81Gz9Pfs^`Y5DBz?6vmx_HnV12g0{E_{{mF zYiw++%gf7aKrL1tJ$h6XJUzr32t?i;b6Z>6-w2$;qobpjnwpw^6i<w<;DVw-Wg<Aa zuCA^Or-#o`9<%cE^WE@Tt5QWT8WxMiJTNdYi8E3@a?(0DIQTB&<JS;iAQm(ftc8b3 z4Gj$!a1uO?`pFG>6^rmeT+nl)sK@|OBWM!fsRhycJ?4MS^#-yL3fv1C1am?rh@!5o zt<9;dtUL*z9vvx-Bi`+Ho3WKs<KyF9&CSiDB7}i<F*qg1(7m9caHZ(EO1zhcM7>`$ z?iPiip54y8`-O%#e1!;=i4uhR#QT{4Vg#Z82rvNN9ch!|eHB;$0000<MNUMnLSTab C$;|=) literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/moreinfo_arrow_pressed.png b/vtm-app/res/drawable-mdpi/moreinfo_arrow_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..529016152379d6b3c016f9cde1d892bbcc701580 GIT binary patch literal 1582 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 literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/no.png b/vtm-app/res/drawable-mdpi/no.png new file mode 100644 index 0000000000000000000000000000000000000000..0ce695e551f857f477d7f394446527236e63540e GIT binary patch literal 443 zcmV;s0Yv_ZP)<h;3K|Lk000e1NJLTq001BW001Be0ssI2{21+{0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzT}ebiRCwC#R@;q(Fc3^m`G*dm1Ly$g zfD)hsIzV(l2cZK>;HUuT;5vW~?nYk8vi!p4?xcKVJ}l$dJMrx7!tM6POX2%3Fin#f z^C5A3GF<+PRaHqV8iKCtE)(Q=F0Fk0x9moOD2m#)#b`Mk1i_O56W1goD4pE*9gi53 zBr!!_mW2{Kjw3Bj4Cgn9<2VdMOfVuVDhHxtJxx=3q|F`@ZQC{s13xTrNYRhuXqv`P zA_Bza?&eyF%BUKu64?k;jx5V8(X|8*`<?2#=H)|L@t>d!Tn{lX+EnD=`S6YIT4=~2 zB1$VR(VYm$Gm;VezBko}AA#gZ4pV*j5vYF0+)^KYbzt$kwm!V;0PV%%cN2$eFB02} z){&xjv9l~o?;Oy1i{C{K*O#E$Do%msA-*%bTg55ndA4u3OE>spxXcG1uj^VAh5iP= l`JN!G4DS!X*TRng0|3f!iSjFnU*G@$002ovPDHLkV1ltt!Z`o{ literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/plane.png b/vtm-app/res/drawable-mdpi/plane.png new file mode 100644 index 0000000000000000000000000000000000000000..4bb11e31582a68258e2c2bb0878ee59c1c3c1533 GIT binary patch literal 407 zcmV;I0cie-P)<h;3K|Lk000e1NJLTq001BW001Be0ssI2{21+{0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzIY~r8RCwC7R_zf)Fc5XQD?kURfDW{v z13EwjbU+DopaVKU1?WHrZf7!QjE}fo$^Obn_Px!1Jkd18_Lh$5x^55zu$ZPve!RZ# zFFyk&h3H^Ok|;!%e1ju8eBoQK<5ANzQRup^M^Uu>0!1#%^8EDj*w)z34JtDXgDUjZ zY}?MV49f&A=<1=E9`&VR7-1N;ZL5l4TO<?#!}fccrYIMNLNAH}dt*jM$6ke^1iP9O zi!9I?=&Pp$0(9QHw*((PO`hiwR1E%EUDr8|a}#D=*JPd9X6bScpgqQM6bEsoz)jOg zkR7p#tu8l{-1mJP$ChP5PV+pU=Xu@Qu8&q#6>iWt)Bi&&KFMfqyMP2GWHh(aXvCR3 zF{tfxp=adeN+3XGiTL=1w1Qy3Rg}DBrPx=10RYUba-!_vze4~3002ovPDHLkV1f`D Buy+6e literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/time.png b/vtm-app/res/drawable-mdpi/time.png new file mode 100644 index 0000000000000000000000000000000000000000..ac323b97016819daf86c064d7ddbd281e5ab2886 GIT binary patch literal 597 zcmV-b0;>IqP)<h;3K|Lk000e1NJLTq001BW001Be0ssI2{21+{0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz_en%SRCwCdmhY9pKorGyo&8P+bPyfT z0Ugi*9XKkW10|pWRDcSU5GvUH9oKnhBs0lG@@3~k#AM#RZ{D9f^Y!)L$9L(9cDp?u zk9)n|a5&V={eHh(F302XbUM`{9}EWD?e;f3(llMIR^4v5GBjQr^zC*71u}pRhr?pA zNRnhSnQS&22t>nVEOhc5vIH7j_v>gha%b=ZXf1}DzflA-!0Sk#*xC7f#)M}HhoST2 zJ->h*c;{!cnc~SliU{NgyCwl(g;4Swn6P6r_oK_czy&NtkV%XiD+Rz`S4H3A2y6`n z<_WI<Lq!6ToIZ!mwDd!-^4#rqHHG2vcmUGrbS#B00?ELBzppPo^0MFWzY!ocXiFpN zhD^BxKlT9*qP$!#OCi?lHF{@%=kxjXdbJ!(r&C^t(wmK%tGG=p$(i96zKX?D?&VGR z>~qw~g{HHx$`_Rf4fio_BnR-hQr5D}kb_T@JvUU4N_AEzc-AM7B4vH$zYc&#;^R*z zI#HyBRowS$iL_8BK}h#QL+e$?Mb)TGNLDN5gY&~qs**}kpuDF_!LYL^e@q2A@Fivw z_^bPe*2fmQc|={gX1camW82MzWtDdPa?Qde2+-B=da=6~lu;L!5Dhy3F@L8cGF-nt jhYs~oBXZK@_!D3Nx^W}TTpQY)00000NkvXXu0mjfrPLDh literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-mdpi/yes.png b/vtm-app/res/drawable-mdpi/yes.png new file mode 100644 index 0000000000000000000000000000000000000000..4af8e767a9ea792c418ee9f908effb9551271c6a GIT binary patch literal 377 zcmV-<0fzpGP)<h;3K|Lk000e1NJLTq001BW001Be0ssI2{21+{0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz8%ab#RCwC#meCD_APk1@jPH)%2)cnf ziH_g~>I9Bp9N{M5+<*;a1UI;AFd?QQO5wo=|0mLret!#8U#~~E($9w&W80h|Vtl2@ z7OSd45e=a0Ix-;7a})_&S{lc3mSyHWflGEF1XPTg?hVkk?NM7i0KmRFPxGY+9POjE z9{{pHO_OK9b^8>6m3<1p%Dy#VYabrLsr?f`(=_mi`o1UI*GyALx~`KXQ3G-q28cva zq$2Y?Ycnt@)38%omIcj7=!uhkQ~{_gOYOuzvahYcbOz%%?&rI${gYd?XF#JM=nb%E z*mgg>vI!V)=5<|j&aLcU1%N(Mgp2b=w@65lzd~u6LX|~P=qqsfpIF7Eo*%$_=`X+l X|3Uwd8AP~300000NkvXXu0mjfBHy3r literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-nodpi/direction.png b/vtm-app/res/drawable-nodpi/direction.png new file mode 100644 index 0000000000000000000000000000000000000000..7056bcfb266d02c059711f62efe2fcdf31b5f51d GIT binary patch literal 1826 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 QkN^Mx07*qoM6N<$f~7iJa{vGU literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-nodpi/ic_launcher.png b/vtm-app/res/drawable-nodpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..5e951fcb7e995db60612aeda971fcef2e55258d2 GIT binary patch literal 3907 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 R3>N?Z002ovPDHLkV1fw*NK^m- literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-nodpi/marker_departure.png b/vtm-app/res/drawable-nodpi/marker_departure.png new file mode 100644 index 0000000000000000000000000000000000000000..3a7eb979c81a72691c781f6545300954c8ab015f GIT binary patch literal 1413 zcmV;01$z34P)<h;3K|Lk000e1NJLTq000yK001Hg1^@s6U#j`N0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU%CrLy>RCwB~Ra<OSMHv3i?%B4xUAw*P zvRjG>v`~swK`sHKmIzG~V^D(;F<uhU2VyYsp&Ao;;Q@?^2?@%B&yv(jgrGswgaD~1 zky>@7TzZGnrQ3E-_j-2EZN`6Q&gpim1Ri>l@60*#|Nl4tZDu66`|}WDC*m<gl|7e5 zL=k5Z!~bU_u@|MZI3Y<s96m(sfAymmLo3%Uhfp|(0t$(^0weu5;lz=X3d+Ak97fyB zJlj%2oLKov=dK=aRYk+IyE~w{z7=FiF7bd+3kKp>;ipsGkebfKzxnKk?P!19A(ZE_ zH@)y~M{o1y#=x!}yZK5cF~9^JDA+5r#Stq)O|bu+COFe`I$S7dm(g|`l3rnQ*(UQo zf1v$*%eJP#&Zj#-HL@iHbGcdYv~7I`3`_T;-)E6yHBO}+4?pnu<%YH=+aRZ>`NUX! z1Xe^=6&V*ilsf{jZSxZ_bY*l2{vJehy75(yIKJ<bJpr%J2fC$kTpzd!Cyss#RjVqX z((ebIXdDJYBSOdduU_hhyq14tEIQG}Jly`&Ya1V|t_eU!OY)@?g*c2%4MI;>FO1$E z1<N#G-5d2#xx5^F?n<aCtAU#GAk=xo5UdE}f|VeZ!2`&(gAr~FHZrm_+{uo^q<S0T z>I9@_G#=XRUlM#YEsKv2zqu5Y$PA3aAh;wqL@FB)%OKQL&BU#Yu#Wmn_#<`^CXtXz ztN;|KRhqo%%Q-&w(-@Cp&F(OG8wtdcV-QQ-LW!S=1B_6L&)$J_%Hoxzf)NStQVb$W z!bV~`lNMyM3OH?}Bq!2P-r{4MVXjf;?;iF9H~9Ftc5eja5D}y%!dA;q>EN_YuQHf} zP@A`;--sP@-FiXPye9(gAVImsJ!5)gUXLmRD*L>`2rtAhWn-&fTCx=UR?$NwtbNoA zYhDh6yO!{<2ztblEMX|b-aSm)rMW>%8*ERXV|~azstBRmtJajE9hQVpkUcU4w^o2u zfhLy78BYVs2mvutk<;;?u|s;&pW|$o+xEKG6L7s=<|z-9)iNa{bz7nUoIv6uCoKU5 zb%?-Be`Yd2Y3^mIbTPIqpEhI@f2!@V-)5EEAOT`9P!7wa{NEWl?C-)&3ghRMbmFgk z2XgQ-yF>v9Y&u+jLy6|w$1f-<NxFoQLxOUP6Ny$~bC8o^tUIAA|Ky+Bbfi8?Oi?01 zXb9uS4@LTB`wMRkem?~+;s&N92ryg&?0?pP5)7T1hUC@4`x_2LdK~-z5HhSk82&nS zUHdA2F<bPIjTV{H$y~EQ`8$6Ob&u0|wnajd+Bvg5z@sQRtg2E$E+s{{i41pspJh59 zvtD()X7(jHqztBL=+@5ASZYA~V)9a^=$XOf1X4FNNDt~iwsr=8C3ATd5tM|?s>r}d zsSqB^#R_>hDdPrgn*ru^s*m4E$DBF7Am*1E3q$4#tUW}Fv>d1%PY>xQ)msJ{VQ;hM z&bc5xtVLVitLvtw(^596RT8qZnmsaQq3~VeT6V75sgXRJJ53y2t7v1{MUm~(hi@ys zH^u1%;tzWCtf|T_kI&F`NDgztGrVw;b1YDCzt~%jd&olNs}bQX4<Ey5+*du59X0v@ z4{wj`VRNj!(PBixP81XF`#IX&v5ancdo6RM`(DcOeGvAiCR8v~emqAPgYaf{N(FV6 zUs~4|gK$PwbSLY#7K1Q@RdouPzL&D=J_u6?)&5Q_hKGz0FJoZ0*z~=W{}o^WF0g$9 Tm*SS000000NkvXXu0mjf=^%?E literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-nodpi/marker_destination.png b/vtm-app/res/drawable-nodpi/marker_destination.png new file mode 100644 index 0000000000000000000000000000000000000000..9b0afcf399b0ac2fccbd0890ddfff7d747b786f8 GIT binary patch literal 1160 zcmV;31b6$1P)<h;3K|Lk000e1NJLTq000yK001Hg1^@s6U#j`N0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$DoI2^RCwB~R()t2Wf=cmn$3Q!?b2(K z)n&^Jg>}+Ra3WagTw6uN3j1ecu*o(RkqQ2x-~^qDf<sW4h(lxu8=?$`OgHD&Emj$= zY<8A)oyOUAHf@^LHqFQ7t7-1e=e^t|m&-?o^o3vEd!LWr`#jJ4-gDtldN>J>6CNPc zsj?_xh~OrS7IWg*k1WeM0moI-bg0eYc*R;>y?V#yRx~%Qfr&F=bb1=!4G+UFL?iu^ zlgG&J1M(GgzWIbCxUa+MJb%B{+VbB1eQ0QVv}owzYgh69i4(Z)n;QGs>wTL1Jq3ic zXl`4(w(pJ34qNxZm-AC7G2F+G<LwI<g8e?<HsTm&gl3hwYMaAx?zP9-ZQaj34<wUW zC%Ft^=ia@5BwF6-xpbB|Sgj)_Rrh+U&ARcKJ<mc;Bmfn%Zr027`p%uZ(7xWWfjACp z88tm@w-25@_>#S`bxU^l4|{*Y_+q;F@vkuBP)BF3rnmpXlSe-Z{x&nSm8HDhQhAS^ zcQr$f$FoMBh)^<B4+W^Vt#hn{v$obkgk6kq>l02V<X9|Iv-x>QV`GR$qbN7hCr^SS zKhEU>*U*5cTpMtDWTcf5@~-+ih=YR=>5j=B52Qc<;jXT-gekW>a|3uD6)i1TQ@N6@ z>XlOtjgDgB;$_IGh3r)cg+gTs+38EnmNpmr`VefgD{@8{yy^F2hf0<$bU0j=P&BMZ zQYkh?7~w##5W}0sw_yoF$*}GblrtD+gujH+>6u9@Z@+goprpc5Zb>FEkQR%QJ!G!P zM#yXfyam2MFi4NRn$5f2Rh6=zO7nlPdsPjK<0D2?p%M-(rqerXtvtUeo=~DRLef1$ z_xz)+5nl(xUOGa050Oxm-i*ygYJk&U*c#;tvi0i0p-@KnhHTdZ^W?i_LSK`ARM2>k z3<NhB(ptft67#QCUS3CSgnZhW?;VxPq+J0q(}I7eonP4h?;I9tsuh{6>Lhhm*7PpF zI3EH<_39XKMbwqTq+J0Ge*>q;@T((ql9G;0duHw86untDMX~oba7^TKx`GJBoZ_%M zEC^FpK2OdZlXbQDY>t%;Zz)7_o?nhoAI%?BQi}5PkE!2@h77)4xdJ}m+%2Hrm_c63 znj)Rs?bGYgcsy>Y&qZlA<I6x$(Mg6tN~Wysb_1B8NX}d)5=}nduo_-j9wJ?jv9?mN zl%>KQPoD`3*~9o@agoWsH%3-)C`KY3;g!i$N=UD&QS_H0O4B_r7D+e1hFm5B$$+cC z9Ho6DP*n*=niR!Z_RqnXs#K(%l^aJ7Y~Cav+5QN-yFE)8o9}|~)<`IfNTz`fEeGMa zm%e~AG36`w=W-Cv`J*wY|607uK`5j|No(YhrHoB?LC8KX+E+cW91$|YDMA%tb}8fk a1Q-C$Dn7Omd`o-)0000<MNUMnLSTY;1T6^w literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-nodpi/marker_node.png b/vtm-app/res/drawable-nodpi/marker_node.png new file mode 100644 index 0000000000000000000000000000000000000000..c4546d59f04adfb7263671c018eeef2043ca2811 GIT binary patch literal 611 zcmV-p0-XJcP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv0006iNkl<Zc-rlh zKWGzC9LK+b4DTSq9XRyH41z6mNFYOQGu<K%w&0LMA+(O2TDw#iO&2XVNDwrkAS8lv zb&5-A-0F520(XlvqYi;Sh;VNRJb&+9qFJgJR7B|SINty7=e_Uylfqw))CJ)G0C;aa zS-w#QOEZYE04P0t*M@t`{pI+dc4)k4NVn<&W6X#{m#Kz+f%>yLws*Eua|3FRYjSII zD;7(~b>b`6Da)8YI#PT6`T3A@0cWE~Ld|$G)6btmyx%EnSoxs``)YpDGj6vmD><O? zsv%3)N|ZBbY=kd^5W&+mEM<gl4}^)O%@!7}Eb6k6HqiOd!N#49R3gCX;8Y5wMJwP+ z{G|)W6v$4T3Ay@BEZl9v&MxXzS<IrmoeU841IY!cj3NY2uEV)3V3W8x-3#(P90EQ; zviCe#mn`j6emS2EaPsj)s?Cc=XE5Hn$YnSfV|qpEh^kbOVsFS0-S()?AP7UgkPvX( zJtm7pd^Cq4oZfwy;NLe?rG$iE*oVm2x~%vsrIloW-3PnUDLAnd*~0h$PQL~==Gt#Q zCh^=<9QuCvJs*3I_L2cqwAwAH?9c#Z3#XNY``R9VxduKJ7x9YR%G!MQV7<C7i^U=g zX6RYRdw`jnOm~IE4a#?pI%vOc&j(PkdUI9Z2Q7~Hyhrp+ZBd}^QJ3DO!}*_z*{D=2 x(#bjccq+@3c-RloJZS#jWA`WG0`Ttv-vB9E*BM9-HXQ%}002ovPDHLkV1fqy9$o+d literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-nodpi/marker_poi_default.png b/vtm-app/res/drawable-nodpi/marker_poi_default.png new file mode 100644 index 0000000000000000000000000000000000000000..6b8d1f1a55cd5366a562b649bed00736740d2132 GIT binary patch literal 610 zcmV-o0-gPdP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv0006hNkl<Zc-rlh zF=$gk7{~vC4)-9!d*IMFW)N(lLjoD{Hq$NQU<(d;R0yqOr`9gjMbkwK4iW@SDF}&R zdFd3F(zw+v83K8WG@}lI98|~+56<uI(%7Z?f{F<J!sGJs?)`uF{qMU=;4eqw3h;jb zg7<+e+$n&~Fo;Od2-FW7@bCG*{QjpCs;{fkFZn=(i8x4H=QL1;%F7Biw>A?~1Io|J za$|i%i)m2`{!Qr7PO={p>3!wZ<&aAOqqCtD7gid6{({2R2Mr6J6p*>;>Urny9mk0e zsJ^Yr{H;7J1){IR{#Pu&=s_gsO$DNd+WI`^Zp`Z3nz7LQ*u>iXwL~nyY5!D02&f9e zK8OE?#BGn1Q)90}*!1i@n8jSm)m1snftzyU0ean@<bsn;!0qQGeqD(xA$#mQl{HY1 zHiCyKIM*EQRc0X*4{-A7M5@gjo{iuiSm4Pt7-PC3)uLu^s9gsg1S=!5h;o8tvM~Y2 ztz#*K(5)E`1RixfaM+U#W%}XR&9>nth0bXOSj;cR18hIumR{B~as=sfhH{r65z~IN zh{V%IT!lIx?M@pzPj})0RMhG<sq9byT?q$vLr>dNUQpuPhznD}a?zje9ITdBWiFS4 z#VoUGvS4!PB8<g{^q*SvsEPW!`g8ylOLv#_dC=l8{XU}Kxh)FRI%=VD*qHvRxERG! wQF>|5wCDIv3cGX;_V<48vilQp1^D-X?=M={8Fd{;o&W#<07*qoM6N<$f~W!}IRF3v literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-nodpi/marker_poi_flickr.png b/vtm-app/res/drawable-nodpi/marker_poi_flickr.png new file mode 100644 index 0000000000000000000000000000000000000000..13d569af3d528670683e239c4da923c460416ae9 GIT binary patch literal 490 zcmV<G0Tup<P)<h;3K|Lk000e1NJLTq000sI000vR1^@s6oswPh00058Nkl<ZILl-B z|NlP&3U*lR2E@HUd>n{BK=E;?m>Y(AC`L9w0f>8m_%jgy$A<qoEc)`*cJ8E?R#TO+ z8w4?AF)jnp@jIIt>yI1NYcen}FrpjOL#!b%e8;?Z5zruBm|?Im`An)ozw8(MSf!nt z2{eccY|vl(MSY|i0>g(4Yjy$+QUGFRhTrxJPLXTSUGv^&Ktr6tUIfO(M{*4U#^5iY zA;~~23G&(}at-=oJLfmhkV+s{WdL$6kZaIo)AkQQL)w5?o8h6wgazapv_K>6A>5!H zdS!_}?dJa^)sSy?bN@t&S)PP@4HQrZ4XSsNYS285)JH(QE8u|*in=Iqt7xEM*NHV` zvu@D~EpF+9K)wC&=m#ezV4`vr)G1$~m3;-|WPIKMxddp)5ulz0P}e{bBsh5klQ|F@ z0C8@Dgzc_{8tM0eG4c@<y&w!?&(}zM6eebV0jOpN)R0`L9xiyM1!X*_^9-Q2^h5o> z1&DV;@fN69KU9qYYNmx10}#WwpiXs$Dz1b^ZyS`I43$%Ws%6A1*boLXLv50TMyoay gt3t&<a?I%Z0D>*&G|VwbegFUf07*qoM6N<$f{i864gdfE literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-nodpi/marker_poi_picasa_24.png b/vtm-app/res/drawable-nodpi/marker_poi_picasa_24.png new file mode 100644 index 0000000000000000000000000000000000000000..1f7e91b564dc1171ed8e13865540ff8881dcf5aa GIT binary patch literal 2295 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 P{GssR$RO1R`@Z-uiGZY- literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-nodpi/marker_poi_wikipedia_16.png b/vtm-app/res/drawable-nodpi/marker_poi_wikipedia_16.png new file mode 100644 index 0000000000000000000000000000000000000000..25cff18ca13aac209bb74afdfa95d76411b7b355 GIT binary patch literal 1474 zcmb7EdrT8|96#f94k2U8Ts0fsj*_8B@6pn0Zxna-u3AQU6r#mA#<h1qp}p&_hkamF zd<3diT!MeN=oB_XjBbo%3qkP};->R4m&HUuc|^%F9gvN&Ic0NqK+!*p$u7D3J@WZ{ zzVC|^<gHGKTM`EVK!SA*WtZ1P<()TI{w^JR!pO^FiFQauLb()RL=GTX!O4MEFH^zU zIfe~=bcxFXfZ3()Vu$3g<q|Hzt6>z3Cg}CaYyikw8T2u(YEA;3T!ouAL8GV6LZF*9 zLF+PYh|Oo_D&1?sB3Bg7D|UseT}BpKnGI$I30c6)NemeDdU!t(G(nTPguGX-VF;Xr zNYy52$|;Af05l6C2WD!J3>TtDKs~BKwK^QfSAZCTA~1r%D3*cZgcc(Z1e|^#*_z0f z6L!inZA(6xph`*d5ilGG1T+DyMi47t)Mzv+8W@%#BQpFUUSfh7ygwzTKyiMT==Mo& zfd>^u#wpZDCP?;lDg>|3W}6Y_{nLq(Qw9eaAB<`c*y~l|nne30JNML$=c4_^As+|Z zIloXNy5xG4r^Lu|?p|z20m|MG`J!7c3ge*!SB;nBB`aluYHHjpLF#ZyN8?%?HybG$ zMGZ7%CP{<QVnNM#CT)%hJWG@%HLT3dMD;k18EML5F{3DrkOopu(nhV3q|LZdr;l;1 zykBB?7pE9ixbCN1>V;gwEOLw_h{b~7iIu;gQji3HrQie2MVa6_H_r+Izfzg0Kq*di zZ{%2uD0soi)DrIJSVu_Afa5f!#Sk5hpy(=0i_@4{PwGgcmd+%hX)gP}(*?_(z)Dj* z(<HGgx$~6mOv}rMnW5o$c~V4qBt8s#<vE@8$V!pL!Ja0&?f9`m)dy$px74O`&|=lT z#`w>7)XiGBLuI%yu;<>Z4Y^mEnwyvUE;mK4l%^bwZ>md*sQ)&cY^dw17`<aUiT}K@ zvGvoy=$*$|4@zpk$Vl6tv+wfIzMP|paXTO8+~)CvKU5sdduQIolAD#f%~iTj-e`AB zq;7pMzUzXsaDIKawdd{TtKrlCE}N?^U2_Hlh!QYy)5JsJdQZP6^^f=4>NaTV^IEsF z*ee~qQD=L@csRPMdF$2k?Q4(k^yqhQJQW=hBNB1-tC52XBQNd!E+t)mAh&^ne$8zy ztkWKdU$JQZ!mQ-R{#&<aomiVL5pOjdIe{k5dr!Tf*?z1nE4h2nhumoWY1x(Xu@vA? zQq<CZhyT%|Z!TUw_*)QJ5-meiDo01}oY&sma<b;-flzYx;YQoBwa0U>mmwFW0mI%y zzuXr)9mBJMgm2bI3(|I!w$)!^w{;H!5o%lb^xpGj=REo)(JjW`%Qh|F@2u|J@U73l zj$E%E8VeZ?UCa0W(>MB8s3rUIjj9X19{u+px810k@aXSG=6B^Bbv|w+dd#&;`!uho zFWNmQcAk4Mj-8*_yfP_n4vXEo`&mocy=!N`UO!xvAId)5&38OG5+8(A9lgU{LFmuN urq#8}whQY0>Y>AtNALo5zPi6}jtUsrb^GSKsoA{pSGCf4)R(Kuw*3cY5*+aW literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-nodpi/marker_poi_wikipedia_32.png b/vtm-app/res/drawable-nodpi/marker_poi_wikipedia_32.png new file mode 100644 index 0000000000000000000000000000000000000000..24c77671b792eb8cef55a34dc4b480d39d971c07 GIT binary patch literal 2117 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 Z&mF*YO@OVhP6j$JcBwcTu?ctN{~rqQOcVeB literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-nodpi/marker_via.png b/vtm-app/res/drawable-nodpi/marker_via.png new file mode 100644 index 0000000000000000000000000000000000000000..cbc2ca6252d2670476da100ea545caf791d8a778 GIT binary patch literal 1119 zcmV-l1fctgP)<h;3K|Lk000e1NJLTq000yK001Hg1^@s6U#j`N000CgNkl<Zc-p0w z3rv$&6vzLztCXS^QARA_)ZvgQLJ%1~n9K(_CL@}t!ABrd$c#>OiZ4W1GK~>mCCUt$ zLDxVeQ4<9vh!2tmYK;$qAVbB?;dBhG0&0-j;<?|0euWmDJ;}+v_uO-T-@W&od-4r{ zbNP&PHfady5W)76w3hS|X_KQx102{?q?<`sX}3ms2L*V;$J-OI0xhjC(9qP1wB33W z&F7QuBJJo8IQqb*>H5JcnQubeS5W&!l1X9j(M^OW29%apqV<KjWn)fNG+F;}1Ps{u z<K*iz!ZqrMImv)Uuv)33klV)875lfUig3~J=gSQ3^(i#0lLFoMq=nv_JkiHDGJ0Xp zPV7F}jlM#2HPvTv+1UJF#-Uqd$<i*mXXR}T&Q4motjD+QX#>>5gPey<2JT*&oIF*G zq^z5JshiG#BS?*ZolEfd(u{-E%6r|fyUMldp&sz|4Tel66Rv=VF+a{m<F?i&{91go zV^_h05C%-lOwtr>NSP%ZLk>%OBkJpn@bk3dp@}zpDuI#g({CA!n95<4OkY6m5y;IS z)?G%%k-t+IaIbEgW<%W9!7!U@=)zk#hu5!cr;9&0o@-0i)x$)Y=tnGkB00XB5A=5i zVqs>pmH`XS{;Wt2A7!?SVrQ2<OcVwcMLE!BuWbhAZpSeOJa%e3k|I9mCoFU=a#C~W z`+*9ju%?R#ewWPy_aFKJ>*FRl52*CWkJ|ywkiI^Zl!f1Gk;{Nf(ifm`WsJQ8%jMo; zw!o<zucUW)0;vq>OM1<a1C3hEN0GZ9$&2gk79vY(mGLbXl(&4#0{D9+I&#k-Oib+T z?4`(>|E0~bgJLvTVZTuPSdR#iE;Rxf$FYyP8%5xtwF$TrItKm|N5byCN+a(`?*0Q@ zZN$I1C14kAk`{-+)!(8zG7w$?BWy+)?b2$twNl%oXB3RvXg_=oQQ+%jpYj#9>Ko|i zML?w%BPkwJn#g2({f6GK0bTiJToKlbQ;rJkVuGGXoWTz)<RS02wW1VP(Q_-oYU<`m z7drwn(d}H0Ws!l7CmS9EYw~b~x-)yvIRlPLjYWN2I8@^El8HyA;GCr0<(Fd`jjBHs z$Yjr!MM#h8RT5cL*Z+o6>c%@-)F04)#cVXjPlda*{GXvc6<kCtjV}COAd^CEDwg<t z$`4p%m*;Tl5#nsY`+)N)H%~_k%NwYo?9kyC>Slf{aQKQCJe)HLDwfC<RJjVuFqQ0v zj|DQ(>wdy?1x2}{2G5UPfIrz@xdJ>$<t>ffW7-i^&^V?)=MMp<Bt|3O%bi|E8DCLc z1;)gNqFheE^9HP>@ljWSVdH}oH<WH>+^FG>Tj45@{bDo<zU=SG=qiw<^#f850e;_A lpd0CJQo2P<AZ_c<`5h9^OrL&42&(`9002ovPDHLkV1njL9<l%c literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-nodpi/pin.png b/vtm-app/res/drawable-nodpi/pin.png new file mode 100644 index 0000000000000000000000000000000000000000..46c70185a9cce81511db93eaa2a518d00b4fdef6 GIT binary patch literal 1725 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 T{S(|)00000NkvXXu0mjfoL5k6 literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-xhdpi/ic_action_search.png b/vtm-app/res/drawable-xhdpi/ic_action_search.png new file mode 100644 index 0000000000000000000000000000000000000000..d699c6b37e0dcb1d636ebdd6733ab6d576d97ab5 GIT binary patch literal 3199 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 ReC-u>pr>W5S)<|b@_%^e3*P_$ literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-xhdpi/ic_launcher.png b/vtm-app/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..436ec1cc2d3be22d0fbaa5d0867ace28c4e87399 GIT binary patch literal 4591 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 Z=l_*8DYSXK3$g$J002ovPDHLkV1j?dsM!Dj literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable-xhdpi/ic_layers.png b/vtm-app/res/drawable-xhdpi/ic_layers.png new file mode 100644 index 0000000000000000000000000000000000000000..bb2ba471e346ba0feae5eccb9b84c7c8ea7525d5 GIT binary patch literal 4940 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 literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable/arrow.png b/vtm-app/res/drawable/arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..8cb329881410f83c6221b07353ff0c1634b3fd49 GIT binary patch literal 10033 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 Hu0mjfniUB< literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable/bkg_compass.png b/vtm-app/res/drawable/bkg_compass.png new file mode 100644 index 0000000000000000000000000000000000000000..7ee7e7b9c319db6343e85bf3865001f5b599bb5d GIT binary patch literal 40821 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 literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable/btn_delete.xml b/vtm-app/res/drawable/btn_delete.xml new file mode 100644 index 00000000..98cf83ba --- /dev/null +++ b/vtm-app/res/drawable/btn_delete.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/delete_pressed" android:state_pressed="true" /> + <item android:drawable="@drawable/delete" android:state_focused="true" /> + <item android:drawable="@drawable/delete" /> +</selector> diff --git a/vtm-app/res/drawable/btn_moreinfo.xml b/vtm-app/res/drawable/btn_moreinfo.xml new file mode 100644 index 00000000..106072e3 --- /dev/null +++ b/vtm-app/res/drawable/btn_moreinfo.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/moreinfo_arrow_pressed" android:state_pressed="true" /> + <item android:drawable="@drawable/moreinfo_arrow" android:state_focused="true" /> + <item android:drawable="@drawable/moreinfo_arrow" /> +</selector> diff --git a/vtm-app/res/drawable/compass.png b/vtm-app/res/drawable/compass.png new file mode 100644 index 0000000000000000000000000000000000000000..7056bcfb266d02c059711f62efe2fcdf31b5f51d GIT binary patch literal 1826 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 QkN^Mx07*qoM6N<$f~7iJa{vGU literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable/globe2.png b/vtm-app/res/drawable/globe2.png new file mode 100644 index 0000000000000000000000000000000000000000..6d788024b03350e799951cc58809c36ee88b905c GIT binary patch literal 6256 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 aJo`V`RkX|Juz#xn0000<MNUMnLSTaVoY_kN literal 0 HcmV?d00001 diff --git a/vtm-app/res/drawable/person.png b/vtm-app/res/drawable/person.png new file mode 100644 index 0000000000000000000000000000000000000000..9f9db93eab6f0548198db9cd2c9dc16d54296ef3 GIT binary patch literal 1531 zcmV<X1qAwuP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00004b3#c}2nYxW zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H11$aqBK~!jg?U_q#RAm&$e`n^-bUJ+uQd(LBM2QLtY6wa~e6W#_ zXkyrz5LT{SXtX98Bre^NurLu9#6;slBaxUGbfE@Rq6Whw5=0P0EEH&`kLkSc<GA>~ z+nK%~b}p?ZJ;}+JJNMr2{Quv1eCK{6s>=UaV)G54QCT1`-?Bj9W+EcXMP!|bIJe72 z<OD=SlF9f>>2zu&lS%)+tf%8>GT{{3+fxTbq$6s3sA{B|$+RBWxZ(b>-%h-zI`)a` z==+-Q+oQcNK2c6LI|D#xl=dXbeZX;?=U1-i+<)emy)CIk6@okj1@H?H6xj9lH`RR~ zoIWx#THP9z%}5D!bhIDc|IQ1Ww?DHMd<(vgfLqPcw&tK)sRmuDIueD=NOPc4sXVx0 zT|0P#;AM@vS$jQ5LWj9`RjLF$6qU_L2_zHo!M?vQ*T7rgwd*YXH!2R$AC=8W33#6O z?WYHiS8J=DouJ1~UZq$HT;Pw$j7C`zfW<9oXYixFkEmYQqI&*O)w2(%p1DiUA6le6 zostKh`%hIN5m^tc1}*`sfP30n;%_{>wyFKur&6g*i_5WJOYX;CdV%Zmq3d%9xB_$l zWmP@gko`soh{%?<mZmQ@ZM@t2@z}-GqD-r-U9++j1U}bq3@1v(yrY39PBP_nb)>!K zR4gc0DzQ_iuQjh&s^y^^GFIT-d{JMY7XqndV$bg0r(b*P)yH}H&4YU8LRr54tQUe3 z1ZB{v<FS)h*}CH^vPa<DU+~xtvblmTnJtEfdz3`H3VxQRgbV6|H(<e*4Ux&CH;fZU zV=*Mv)Ts3c2oMy(8?vCQrus|<T~8>8Wd9nM1_=ZvJp3UuY5@MA0X${(Z7BJ<Xpmcp z#shxn0Wlt!F8)IzULgY5HHj{Sv<w=b7($bYXcZzpLufpNf)e<y1(~!2CdEB|Bw(kL z#yy}#@I6z36q^G!T;@O~5D*ao;`Vn3(<_O+?E{`2eN_zr3)ViNF8IT90J|m*fQZC_ zB+v>hvf2iu>@xvS2F8F9U_?a5niAvWtjzr(nPWe=TY*lX2UrTUV+h5mc_09)7!o&t zKKsnPN*E>y%xbp+X`rd@9+;&S4B%#<1>>dk9OIoLF-%<NfUCfH;2fs2yp9Q%0w#Pa zcC2CuF82c3zdMe5_D}f!IGRjMoV<S!cI+ak|1FV#d<i*q4vwA#g;FB~990cOq-sf& zfnkg%GFIC$E1t%9pv`K`_xb6E6Rh8~lWyOq)Vq`J`pE}|;Pb;14R)J@mGb7DR3%2a zP-1&S1VkhZp;k<fz67{~S`sHPxe#h70(fHu_Sx%gu^8LxC(oClP$K3B5U>08iwV_2 zNi&TAAD;^o-(if0L>61kFfk{jPH$Oq@<lU_j!n5Gw(>PBj;gANxR?;~FfmyGx-p5G zqE?MU&>@MNN=RTh2aD_0+fI@ck+L@UN$3FBNva~^Ve%k{$%P(Fc@4|zOosr|f(QwW zO!WYO+bDRLv=iHeu3$V-1V%ALmSOUv1ruiB>eqW@isWs5nSp>KBEk5M9t8my_l74T zVe62`l+$jEH(Kl7i(?2mOyh-{cpwJc31mg22zaw4GGjA0O;}|y2_1SMj`2vR)hR9K zSiuyB83^2mIi0u$jEYEgHX(J(Zq%+!1*nKfj#{{cJM#dh{TQA6?nn;b5|~_Z6tgIS zohNJ>|22Uq&V>?~_F{K;2e0&W;aV_Ux`H#Dqrazvo}Ml$fVv)Xp^r+j#A?8`>d!Wz zBRQS6ZFn2-B(;45%qss3tP6o+R{gT7UToMf5g)HxA~9;^Hc-{u_c=zy1m;5){-vC6 hSs*aqvOr+I<sXMcqVh;Ym)rmV002ovPDHLkV1kHCx4r-X literal 0 HcmV?d00001 diff --git a/vtm-app/res/layout/activity_file_picker.xml b/vtm-app/res/layout/activity_file_picker.xml new file mode 100644 index 00000000..b47fa3b5 --- /dev/null +++ b/vtm-app/res/layout/activity_file_picker.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<GridView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/filePickerView" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:fadingEdge="vertical" + android:fadingEdgeLength="30dip" + android:columnWidth="100dip" + android:numColumns="auto_fit" /> \ No newline at end of file diff --git a/vtm-app/res/layout/activity_tilemap.xml b/vtm-app/res/layout/activity_tilemap.xml new file mode 100644 index 00000000..489be4ce --- /dev/null +++ b/vtm-app/res/layout/activity_tilemap.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/mainView" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:windowActionBarOverlay="true"> + + <org.oscim.android.MapView + android:id="@+id/mapView" + android:layout_width="fill_parent" + android:layout_height="fill_parent" /> + + <RelativeLayout + android:id="@+id/route_bar" + android:layout_width="fill_parent" + android:layout_height="25dip" + android:background="#dd000000" + android:orientation="horizontal"> + + <ImageView + android:id="@+id/route_bar_distance_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/plane" /> + + <TextView + android:id="@+id/route_bar_distance" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_toRightOf="@+id/route_bar_distance_icon" + android:gravity="center" + android:textColor="#fff" + android:textSize="14sp" /> + + <ImageView + android:id="@+id/route_bar_route_length_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toRightOf="@+id/route_bar_distance" + android:src="@drawable/car" /> + + <TextView + android:id="@+id/route_bar_route_length" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_toRightOf="@+id/route_bar_route_length_icon" + android:gravity="center" + android:textColor="#fff" + android:textSize="14sp" /> + + <ImageView + android:id="@+id/route_bar_travel_time_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toRightOf="@+id/route_bar_route_length" + android:src="@drawable/time" /> + + <TextView + android:id="@+id/route_bar_travel_time" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_toRightOf="@+id/route_bar_travel_time_icon" + android:gravity="center" + android:textColor="#fff" + android:textSize="14sp" /> + + <ImageView + android:id="@+id/route_bar_clear" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:clickable="true" + android:src="@drawable/no" /> + </RelativeLayout> + + <ToggleButton + android:id="@+id/snapToLocationView" + android:layout_width="57dp" + android:layout_height="72dp" + android:layout_alignParentBottom="true" + android:layout_alignParentRight="true" + android:layout_marginBottom="10dip" + android:layout_marginRight="10dip" + android:textOff="" + android:textOn="" + android:visibility="gone" /> + + <TextView + android:id="@+id/mapInfo" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="left" + android:layout_marginLeft="70dip" + android:layout_marginTop="25dip" + android:background="@drawable/info_window" + android:clickable="true" + android:ellipsize="end" + android:maxEms="17" + android:onClick="onClick" + android:textColor="#404040" + android:visibility="invisible" /> + + <!-- android:background="@drawable/snap_to_position" --> + + + <!-- + <ImageView + android:id="@+id/imageView1" + android:layout_width="64px" + android:layout_height="64px" + android:layout_alignParentLeft="true" + android:layout_alignTop="@+id/mapInfo" + android:src="@drawable/compass_bg" /> + --> + + <ImageView + android:id="@+id/compass" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_alignBottom="@+id/mapInfo" + android:layout_alignParentLeft="true" + android:layout_marginLeft="24dp" + android:onClick="toggleLocation" + android:src="@drawable/compass" /> + +</RelativeLayout> \ No newline at end of file diff --git a/vtm-app/res/layout/bonuspack_bubble.xml b/vtm-app/res/layout/bonuspack_bubble.xml new file mode 100644 index 00000000..d7b154e6 --- /dev/null +++ b/vtm-app/res/layout/bonuspack_bubble.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="10dp" + android:background="@drawable/bonuspack_bubble" + android:orientation="horizontal"> + + <ImageView + android:id="@+id/bubble_image" + android:layout_width="48dp" + android:layout_height="48dp" + android:paddingLeft="5dp" + android:paddingRight="5dp" + android:scaleType="center" + android:visibility="gone" /> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:orientation="vertical" + android:paddingLeft="5dp"> + + <TextView + android:id="@+id/bubble_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="left" + android:ellipsize="end" + android:maxEms="17" + android:textColor="#404040" /> + + <TextView + android:id="@+id/bubble_description" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxEms="17" + android:textColor="#404040" + android:textSize="12dp" /> + + <TextView + android:id="@+id/bubble_subdescription" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxEms="17" + android:textColor="#404040" + android:textSize="10dp" + android:visibility="gone" /> + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:orientation="horizontal" + android:paddingLeft="5dp"> + + <Button + android:id="@+id/bubble_moreinfo" + android:layout_width="24dp" + android:layout_height="24dp" + android:background="@drawable/btn_moreinfo" + android:visibility="gone" /> + </LinearLayout> + +</LinearLayout> \ No newline at end of file diff --git a/vtm-app/res/layout/dialog_enter_coordinates.xml b/vtm-app/res/layout/dialog_enter_coordinates.xml new file mode 100644 index 00000000..23b2d555 --- /dev/null +++ b/vtm-app/res/layout/dialog_enter_coordinates.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:padding="20dip"> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/latitude" + android:textStyle="bold" + android:textColor="#FFF" /> + + <EditText + android:id="@+id/latitude" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:maxLength="9" + android:inputType="numberSigned|numberDecimal" /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/longitude" + android:textStyle="bold" + android:textColor="#FFF" /> + + <EditText + android:id="@+id/longitude" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:maxLength="9" + android:inputType="numberSigned|numberDecimal" /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/zoomLevel" + android:textStyle="bold" + android:textColor="#FFF" /> + + <SeekBar + android:id="@+id/zoomLevel" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/zoomlevelValue" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:textStyle="bold" + android:gravity="center_horizontal" /> + </LinearLayout> +</ScrollView> \ No newline at end of file diff --git a/vtm-app/res/layout/dialog_info_map_file.xml b/vtm-app/res/layout/dialog_info_map_file.xml new file mode 100644 index 00000000..8689fee0 --- /dev/null +++ b/vtm-app/res/layout/dialog_info_map_file.xml @@ -0,0 +1,155 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:padding="20dip"> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/info_map_file_name" + android:textStyle="bold" + android:textColor="#FFF" /> + + <TextView + android:id="@+id/infoMapFileViewName" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="10dip" /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/info_map_file_size" + android:textStyle="bold" + android:textColor="#FFF" /> + + <TextView + android:id="@+id/infoMapFileViewSize" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="10dip" /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/info_map_file_version" + android:textStyle="bold" + android:textColor="#FFF" /> + + <TextView + android:id="@+id/infoMapFileViewVersion" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="10dip" /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/info_map_file_debug" + android:textStyle="bold" + android:textColor="#FFF" /> + + <TextView + android:id="@+id/infoMapFileViewDebug" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="10dip" /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/info_map_file_date" + android:textStyle="bold" + android:textColor="#FFF" /> + + <TextView + android:id="@+id/infoMapFileViewDate" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="10dip" /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/info_map_file_area" + android:textStyle="bold" + android:textColor="#FFF" /> + + <TextView + android:id="@+id/infoMapFileViewArea" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="10dip" /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/info_map_file_start_position" + android:textStyle="bold" + android:textColor="#FFF" /> + + <TextView + android:id="@+id/infoMapFileViewStartPosition" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="10dip" /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/info_map_file_start_zoom_level" + android:textStyle="bold" + android:textColor="#FFF" /> + + <TextView + android:id="@+id/infoMapFileViewStartZoomLevel" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="10dip" /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/info_map_file_language_preference" + android:textStyle="bold" + android:textColor="#FFF" /> + + <TextView + android:id="@+id/infoMapFileViewLanguagePreference" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="10dip" /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/info_map_file_comment" + android:textStyle="bold" + android:textColor="#FFF" /> + + <TextView + android:id="@+id/infoMapFileViewComment" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="10dip" /> + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/info_map_file_created_by" + android:textStyle="bold" + android:textColor="#FFF" /> + + <TextView + android:id="@+id/infoMapFileViewCreatedBy" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingBottom="10dip" /> + </LinearLayout> +</ScrollView> \ No newline at end of file diff --git a/vtm-app/res/layout/item_layout.xml b/vtm-app/res/layout/item_layout.xml new file mode 100644 index 00000000..3c4e029a --- /dev/null +++ b/vtm-app/res/layout/item_layout.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <ImageView + android:id="@+id/thumbnail" + android:layout_width="65dp" + android:layout_height="65dp" + android:scaleType="center" + android:src="@drawable/ic_continue" /> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="instructions" + android:textColor="#000000" /> + + <TextView + android:id="@+id/details" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="distance/duration" + android:textColor="#555555" /> + </LinearLayout> + +</LinearLayout> \ No newline at end of file diff --git a/vtm-app/res/layout/items_list.xml b/vtm-app/res/layout/items_list.xml new file mode 100644 index 00000000..6a74bf69 --- /dev/null +++ b/vtm-app/res/layout/items_list.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="#55000000" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_gravity="center|clip_vertical" + android:layout_marginBottom="10dp" + android:layout_marginLeft="10dp" + android:layout_marginRight="10dp" + android:layout_marginTop="10dp" + android:background="#FFFFFF" + android:orientation="vertical" + android:padding="10dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <AutoCompleteTextView + android:id="@+id/poiTag" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:completionThreshold="1" + android:ems="10" /> + + <!-- <Button + android:id="@+id/buttonSetPOITag" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Search" /> --> + </LinearLayout> + + <HorizontalScrollView + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="40dp" + android:orientation="horizontal"> + + <Button + android:id="@+id/pois_btn_nominatim" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Places" /> + + <Button + android:id="@+id/pois_btn_wikipedia" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Wikipedia" /> + + <Button + android:id="@+id/pois_btn_flickr" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Flickr" /> + + <Button + android:id="@+id/pois_btn_foursquare" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Foursquare" /> + </LinearLayout> + </HorizontalScrollView> + + <ListView + android:id="@+id/items" + android:layout_width="fill_parent" + android:layout_height="fill_parent" /> + </LinearLayout> + +</LinearLayout> \ No newline at end of file diff --git a/vtm-app/res/layout/itinerary_bubble.xml b/vtm-app/res/layout/itinerary_bubble.xml new file mode 100644 index 00000000..29311e76 --- /dev/null +++ b/vtm-app/res/layout/itinerary_bubble.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:background="@drawable/bonuspack_bubble"> + + <ImageView + android:id="@+id/bubble_image" + android:layout_width="65dp" + android:layout_height="65dp" + android:paddingLeft="5dp" + android:visibility="gone" /> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:id="@+id/bubble_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="#404040" + android:maxEms="17" + android:text="Title" /> + </LinearLayout> + + <TextView + android:id="@+id/bubble_description" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="#404040" + android:textSize="12dp" + android:maxEms="17" + android:text="Description" /> + + <TextView + android:id="@+id/bubble_subdescription" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="#404040" + android:textSize="10dp" + android:maxEms="17" + android:text="Address" + android:visibility="gone" /> + </LinearLayout> + + <Button + android:id="@+id/bubble_delete" + android:background="@drawable/btn_delete" + android:visibility="visible" + android:layout_width="25dp" + android:layout_height="25dp" + android:layout_gravity="right" /> +</LinearLayout> diff --git a/vtm-app/res/layout/seek_bar_preference.xml b/vtm-app/res/layout/seek_bar_preference.xml new file mode 100644 index 00000000..4dd6ae4e --- /dev/null +++ b/vtm-app/res/layout/seek_bar_preference.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@android:id/widget_frame" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingLeft="15dp" + android:paddingTop="5dp" + android:paddingRight="10dp" + android:paddingBottom="5dp"> + + <TextView + android:id="@android:id/title" + android:layout_alignParentLeft="true" + android:layout_alignParentTop="true" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:textSize="22dp" + android:typeface="sans" + android:textStyle="normal" + android:textColor="#ffffff"></TextView> + + <TextView + android:id="@android:id/summary" + android:layout_alignParentLeft="true" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_below="@android:id/title"></TextView> + + <TextView + android:id="@+id/seekBarPrefUnitsRight" + android:layout_alignParentRight="true" + android:layout_below="@android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content"></TextView> + + <TextView + android:id="@+id/seekBarPrefValue" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toLeftOf="@id/seekBarPrefUnitsRight" + android:layout_below="@android:id/title" + android:gravity="right"></TextView> + + <TextView + android:id="@+id/seekBarPrefUnitsLeft" + android:layout_below="@android:id/title" + android:layout_toLeftOf="@id/seekBarPrefValue" + android:layout_width="wrap_content" + android:layout_height="wrap_content"></TextView> + + <LinearLayout + android:id="@+id/seekBarPrefBarContainer" + android:layout_alignParentLeft="true" + android:layout_alignParentBottom="true" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_below="@android:id/summary"></LinearLayout> + +</RelativeLayout> diff --git a/vtm-app/res/menu/map_menu.xml b/vtm-app/res/menu/map_menu.xml new file mode 100644 index 00000000..86438ec8 --- /dev/null +++ b/vtm-app/res/menu/map_menu.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:id="@+id/menu_poi_nearby" + android:title="@string/menu_find" /> + <item + android:id="@+id/menu_route_departure" + android:title="Set Departure" /> + <item + android:id="@+id/menu_route_destination" + android:title="Set Destination" /> + <item + android:id="@+id/menu_route_viapoint" + android:title="Add Via-Point" /> + <item + android:id="@+id/menu_route_clear" + android:title="Clear Route" /> + <item + android:id="@+id/menu_poi_clear" + android:title="Clear POIs" /> +</menu> \ No newline at end of file diff --git a/vtm-app/res/menu/options_menu.xml b/vtm-app/res/menu/options_menu.xml new file mode 100644 index 00000000..1ceb16c7 --- /dev/null +++ b/vtm-app/res/menu/options_menu.xml @@ -0,0 +1,106 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:id="@+id/menu_position" + android:icon="@drawable/ic_menu_mylocation" + android:showAsAction="always" + android:title="@string/menu_position"> + <menu> + <item + android:id="@+id/menu_poi_nearby" + android:title="@string/menu_find" /> + <item + android:id="@+id/menu_position_my_location_enable" + android:checkable="true" + android:title="@string/menu_position_my_location_enable" /> + <item + android:id="@+id/menu_position_follow_location" + android:checkable="true" + android:title="@string/menu_position_follow_location" /> + <item + android:id="@+id/menu_compass_2d" + android:checkable="true" + android:title="@string/menu_compass_2d" /> + <item + android:id="@+id/menu_compass_3d" + android:checkable="true" + android:title="@string/menu_compass_3d" /> + + <!-- + <item + android:id="@+id/menu_compass_enable" + android:checkable="true" + android:title="@string/menu_compass_enable"/> + --> + <item + android:id="@+id/menu_position_enter_coordinates" + android:title="@string/menu_position_enter_coordinates" /> + + <!-- + <item + android:id="@+id/menu_position_last_known" + android:title="@string/menu_position_last_known"/> + --> + + <item + android:id="@+id/menu_position_map_center" + android:title="@string/menu_position_map_file_center" /> + </menu> + </item> + <!-- + <item + android:id="@+id/menu_options" + android:icon="@drawable/ic_menu_options" + android:showAsAction="always" + android:title="@string/menu_options"> + <menu> + --> + <!-- android:icon="@drawable/ic_menu_preferences" --> + <item + android:id="@+id/menu_preferences" + android:icon="@drawable/ic_menu_options" + android:showAsAction="never" + android:title="@string/menu_settings" /> + + <!-- android:icon="@drawable/ic_menu_mapmode" --> + <!-- android:icon="@drawable/ic_menu_archive" --> + <!-- + <item + android:id="@+id/menu_mapfile" + android:showAsAction="never" + android:title="@string/menu_mapfile"/> + --> + <!-- + <item + android:id="@+id/menu_info_map_file" + android:title="@string/menu_info_map_file"/> + --> + <!-- + </menu> + </item> + --> + <item + android:id="@+id/menu_info_about" + android:icon="@drawable/info" + android:title="@string/menu_info_about" /> + <item + android:id="@+id/menu_layers" + android:icon="@drawable/ic_layers" + android:title="@string/menu_layers"> + <menu> + <item + android:id="@+id/menu_layer_naturalearth" + android:checkable="true" + android:title="@string/menu_layer_naturalearth" /> + <item + android:id="@+id/menu_layer_openstreetmap" + android:checkable="true" + android:title="@string/menu_layer_openstreetmap" /> + <item + android:id="@+id/menu_layer_grid" + android:checkable="true" + android:title="@string/menu_layer_grid" /> + </menu> + </item> + +</menu> \ No newline at end of file diff --git a/vtm-app/res/menu/poi_menu.xml b/vtm-app/res/menu/poi_menu.xml new file mode 100644 index 00000000..3d5f13d0 --- /dev/null +++ b/vtm-app/res/menu/poi_menu.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:id="@+id/menu_link" + android:title="Open URL" /> + <item + android:id="@+id/menu_none" + android:title="Cancel" /> +</menu> \ No newline at end of file diff --git a/vtm-app/res/values-v11/styles.xml b/vtm-app/res/values-v11/styles.xml new file mode 100644 index 00000000..d408cbc3 --- /dev/null +++ b/vtm-app/res/values-v11/styles.xml @@ -0,0 +1,5 @@ +<resources> + + <style name="AppTheme" parent="android:Theme.Holo.Light" /> + +</resources> \ No newline at end of file diff --git a/vtm-app/res/values-v14/styles.xml b/vtm-app/res/values-v14/styles.xml new file mode 100644 index 00000000..1c089a78 --- /dev/null +++ b/vtm-app/res/values-v14/styles.xml @@ -0,0 +1,5 @@ +<resources> + + <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar" /> + +</resources> \ No newline at end of file diff --git a/vtm-app/res/values/poi_tags.xml b/vtm-app/res/values/poi_tags.xml new file mode 100644 index 00000000..4e016bc7 --- /dev/null +++ b/vtm-app/res/values/poi_tags.xml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources> + <!-- OSM tags relevant for a Nominatim search by feature --> + <!-- See http://wiki.openstreetmap.org/wiki/Map_Features --> + <!-- in comment, those with 0 match for Paris area on 16/07/2012 --> + <string-array name="poi_tags"> + <!-- Aeroway --> + <!-- <item>aerodrome</item> --> + <!-- Amenity --> + <item>bar</item> + <item>cafe</item> + <!-- <item>fast_food</item> --> + <item>pub</item> + <item>restaurant</item> + <item>library</item> + <item>school</item> + <item>university</item> + <!-- <item>bicycle_parking</item> --> + <item>bus_station</item> + <!-- <item>car_rental</item> --> + <item>car_sharing</item> + <item>car_wash</item> + <!-- <item>ev_charging</item> --> + <item>fuel</item> + <item>parking</item> + <item>taxi</item> + <item>atm</item> + <item>bank</item> + <item>dentist</item> + <item>hospital</item> + <item>pharmacy</item> + <!-- <item>veterinary</item> --> + <item>cinema</item> + <!-- <item>nightclub</item> --> + <item>theatre</item> + <!-- <item>fire_station</item> --> + <item>marketplace</item> + <item>police</item> + <!-- <item>post_box</item> --> + <!-- <item>post_office</item> --> + <!-- <item>recycling</item> --> + <!-- <item>sauna</item> --> + <item>telephone</item> + <item>toilets</item> + <!-- Building --> + <item>hotel</item> + <item>church</item> + <!-- <item>train_station</item> => station prefered --> + <!-- Craft --> + <!-- Emergency --> + <!-- <item>ambulance_station</item> --> + <!-- <item>phone</item> --> + <!-- Highway --> + <!-- <item>speed_camera</item> --> + <!-- Historic --> + <item>castle</item> + <item>monument</item> + <!-- Land use --> + <item>cemetery</item> + <!-- Leisure --> + <!-- <item>dance</item> --> + <!-- <item>golf_course</item> --> + <item>park</item> + <item>stadium</item> + <!-- <item>swimming_pool</item> --> + <!-- Natural --> + <item>beach</item> + <!-- Public Transport --> + <item>station</item> + <!-- Shop --> + <item>bakery</item> + <item>butcher</item> + <!-- <item>car_repair</item> --> + <!-- <item>clothes</item> --> + <!-- <item>convenience</item> --> + <item>florist</item> + <item>hairdresser</item> + <!-- <item>jewelry</item> --> + <item>kiosk</item> + <item>laundry</item> + <item>mall</item> + <item>optician</item> + <!-- <item>shoes</item> --> + <item>supermarket</item> + <!-- Sport --> + <!-- <item>golf</item> --> + <!-- <item>horse_racing</item> --> + <!-- <item>judo</item> --> + <!-- <item>karting</item> --> + <!-- <item>tennis</item> --> + <!-- Tourism --> + <item>information</item> + <item>museum</item> + <item>viewpoint</item> + <item>zoo</item> + <!-- Non-Nominatim tags, for other services: --> + <item>wikipedia</item> + <item>flickr</item> + <item>picasa</item> + <item>foursquare</item> + </string-array> +</resources> diff --git a/vtm-app/res/values/route.xml b/vtm-app/res/values/route.xml new file mode 100644 index 00000000..acc78aab --- /dev/null +++ b/vtm-app/res/values/route.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="route_lookup_error">Route search failed</string> +</resources> diff --git a/vtm-app/res/values/settings.xml b/vtm-app/res/values/settings.xml new file mode 100644 index 00000000..de4977ba --- /dev/null +++ b/vtm-app/res/values/settings.xml @@ -0,0 +1,115 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources> + + <string-array name="preferences_map_database_keys"> + <item>OPENSCIENCEMAP1</item> + <item>OPENSCIENCEMAP2</item> + <item>OPENSCIENCEMAP4</item> + <item>MAPNIK_VECTOR</item> + <item>MAPSFORGE</item> + <!-- <item>POSTGIS_READER</item> --> + </string-array> + <string-array name="preferences_map_generator_values"> + <item>OpenScienceMap1</item> + <item>OpenScienceMap2</item> + <item>OpenScienceMap4</item> + <item>Mapnik-Vector</item> + <item>Mapsforge File</item> + <!-- <item>PostGIS</item> --> + </string-array> + + <string name="preferences_map_database_default">OSCIMAP_READER</string> + + <string-array name="preferences_map_theme_keys"> + <item>DEFAULT</item> + <item>TRONRENDER</item> + <item>OSMARENDER</item> + <!-- <item>MAPNIK</item> --> + </string-array> + <string-array name="preferences_map_theme_values"> + <item>Default</item> + <item>Tubes</item> + <item>Osmarender</item> + <!-- <item>Mapnik</item> --> + </string-array> + + <string name="preferences_map_theme_default">OSMARENDER</string> + <string name="preferences_debug">Debug settings</string> + <string name="preferences_theme_title">Theme settings</string> + <string name="preferences_theme">Theme</string> + <string name="preferences_theme_summary">Change the theme of the map</string> + <string name="preferences_about">About</string> + <string name="preferences_about_summary">Show us!</string> + <string name="preferences_general">General settings</string> + <string name="preferences_cache_persistence">Cache persistence</string> + <string name="preferences_cache_persistence_desc">Keep cached images on exit</string> + <string name="preferences_cache_size">External storage</string> + <string name="preferences_cache_size_desc">Adjust the size of the cache</string> + <string name="preferences_cache_size_value">%.1f MB</string> + <string name="preferences_fullscreen">Full screen mode</string> + <string name="preferences_fullscreen_desc">Hide the status bar</string> + <string name="preferences_fix_orientation">Fix screen orientation</string> + <string name="preferences_fix_orientation_desc">Fix screen orientation</string> + <string name="preferences_map">Map settings</string> + <string name="preferences_map_view_mode">Map source</string> + <string name="preferences_map_view_mode_desc">Select map data source</string> + <string name="preferences_move_speed">Move speed</string> + <string name="preferences_move_speed_desc">Adjust the move speed of the map</string> + <string name="preferences_move_speed_value">%d %% move speed</string> + <string name="preferences_scale_bar_unit">Scale bar unit</string> + <string name="preferences_scale_bar_unit_desc">Select the unit for the map scale bar</string> + <string name="preferences_show_fps_counter">Frame rate</string> + <string name="preferences_show_fps_counter_desc">Enable frames per second counter</string> + <string name="preferences_show_scale_bar">Map scale bar</string> + <string name="preferences_show_scale_bar_desc">Show the scale of the map</string> + <string name="preferences_show_tile_coordinates">Tile coordinates</string> + <string name="preferences_show_tile_coordinates_desc">Show coordinates on tiles</string> + <string name="preferences_show_unmatched_ways">Draw unmatched ways</string> + <string name="preferences_show_tile_frames">Tile boundaries</string> + <string name="preferences_show_tile_frames_desc">Draw tile boundaries</string> + <string name="preferences_disable_polygons">Disable Polygon rendering</string> + <string name="preferences_debug_labels">Debug Labels</string> + <string name="preferences_show_water_tiles_desc">Highlight tiles which have the water flag set</string> + <string name="preferences_text_scale">Font size</string> + <string name="preferences_text_scale_desc">Select the text size of map labels</string> + <string name="preferences_wake_lock">Stay awake</string> + <string name="preferences_wake_lock_desc">Stop the screen from dimming</string> + + <!-- + <string-array name="view_sections"> + <item>Map</item> + <item>Routes</item> + <item>Overlays</item> + <item>etc</item> + </string-array> + + <string-array name="preferences_scale_bar_unit_keys"> + <item>imperial</item> + <item>metric</item> + </string-array> + <string-array name="preferences_scale_bar_unit_values"> + <item>Imperial</item> + <item>Metric</item> + </string-array> + + <string name="preferences_scale_bar_unit_default">metric</string> + + <string-array name="preferences_text_scale_keys"> + <item>0.7</item> + <item>0.85</item> + <item>1.0</item> + <item>1.3</item> + <item>1.6</item> + </string-array> + <string-array name="preferences_text_scale_values"> + <item>tiny</item> + <item>small</item> + <item>normal</item> + <item>large</item> + <item>huge</item> + </string-array> + + <string name="preferences_text_scale_default">1.0</string> + --> + +</resources> \ No newline at end of file diff --git a/vtm-app/res/values/strings.xml b/vtm-app/res/values/strings.xml new file mode 100755 index 00000000..3005c92d --- /dev/null +++ b/vtm-app/res/values/strings.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources> + + <string name="application_name">OpenScienceMap</string> + <string name="ok">OK</string> + <string name="cancel">Cancel</string> + <string name="error">Error</string> + <string name="error_last_location_unknown">The last location is unknown</string> + <string name="file_invalid">The selected file is invalid.</string> + <string name="file_select">Please select a file.</string> + <string name="file_size_byte">byte</string> + <string name="file_size_bytes">bytes</string> + <string name="file_size_kb">kB</string> + <string name="file_size_mb">MB</string> + <string name="file_size_gb">GB</string> + <string name="go_to_position">Go to position</string> + <string name="info_map_file_area">Area</string> + <string name="info_map_file_comment">Comment</string> + <string name="info_map_file_created_by">Created by</string> + <string name="info_map_file_date">Date</string> + <string name="info_map_file_debug">Debug information</string> + <string name="info_map_file_debug_no">not included</string> + <string name="info_map_file_debug_yes">included</string> + <string name="info_map_file_language_preference">Language preference</string> + <string name="info_map_file_name">File</string> + <string name="info_map_file_size">File size</string> + <string name="info_map_file_start_position">Start position</string> + <string name="info_map_file_start_zoom_level">Start zoom level</string> + <string name="info_map_file_version">Version</string> + <string name="latitude">Latitude</string> + <string name="longitude">Longitude</string> + <string name="menu_info">Info</string> + <string name="menu_settings">Settings</string> + <string name="menu_layers">Layers</string> + <string name="menu_layer_grid">Grid</string> + <string name="menu_layer_naturalearth">Natural Earth</string> + <string name="menu_layer_openstreetmap">OpenStreetMap</string> + <string name="menu_info_map_file">Map file properties</string> + <string name="menu_find">Find</string> + <string name="menu_info_about">About</string> + <string name="menu_mapfile">Map file</string> + <string name="menu_position">Position</string> + <string name="menu_compass_enable">Compass</string> + <string name="menu_position_enter_coordinates">Enter coordinates</string> + <string name="menu_position_last_known">Last known location</string> + <string name="menu_position_map_file_center">Map file center</string> + <string name="menu_position_my_location_enable">Show my location</string> + <string name="menu_position_follow_location">Follow my location</string> + <string name="menu_compass">Compass</string> + <string name="menu_compass_2d">Compass 2D</string> + <string name="menu_compass_3d">Compass 3D</string> + <string name="menu_compass_off">Compass Off</string> + <string name="menu_preferences">Preferences</string> + <string name="menu_render_theme">Theme</string> + <string name="no_location_provider_available">No location source available</string> + <string name="snap_to_location_enabled">Snap to position is activated</string> + <string name="snap_to_location_disabled">Snap to position is disabled</string> + <string name="unit_symbol_kilometer"> km</string> + <string name="unit_symbol_meter"> m</string> + <string name="zoomLevel">Zoom level</string> + <string name="menu_options">Options</string> + <string name="menu_pois">Bars nearby</string> + <string name="menu_poi_list">List POIs</string> + <string name="departure">Departure</string> + <string name="destination">Destination</string> + <string name="viapoint">Via-Point</string> + +</resources> \ No newline at end of file diff --git a/vtm-app/res/values/styles.xml b/vtm-app/res/values/styles.xml new file mode 100644 index 00000000..4dba0d0a --- /dev/null +++ b/vtm-app/res/values/styles.xml @@ -0,0 +1,5 @@ +<resources> + + <style name="AppTheme" parent="android:Theme.Light" /> + +</resources> \ No newline at end of file diff --git a/vtm-app/res/xml/preferences.xml b/vtm-app/res/xml/preferences.xml new file mode 100644 index 00000000..a00d48a4 --- /dev/null +++ b/vtm-app/res/xml/preferences.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:oscim_app="http://app.oscim.org" + android:title="@string/menu_preferences"> + + <!-- <PreferenceCategory android:title="@string/preferences_map" > --> + <!-- + <CheckBoxPreference + android:key="showScaleBar" + android:summary="@string/preferences_show_scale_bar_desc" + android:title="@string/preferences_show_scale_bar" /> + <ListPreference + android:defaultValue="@string/preferences_scale_bar_unit_default" + android:dependency="showScaleBar" + android:entries="@array/preferences_scale_bar_unit_values" + android:entryValues="@array/preferences_scale_bar_unit_keys" + android:key="scaleBarUnit" + android:summary="@string/preferences_scale_bar_unit_desc" + android:title="@string/preferences_scale_bar_unit" /> + --> + + + <ListPreference + android:defaultValue="@string/preferences_map_database_default" + android:entries="@array/preferences_map_generator_values" + android:entryValues="@array/preferences_map_database_keys" + android:key="mapDatabase" + android:summary="@string/preferences_map_view_mode_desc" + android:title="@string/preferences_map_view_mode" /> + + + <!-- + <ListPreference + android:defaultValue="@string/preferences_text_scale_default" + android:entries="@array/preferences_text_scale_values" + android:entryValues="@array/preferences_text_scale_keys" + android:key="textScale" + android:summary="@string/preferences_text_scale_desc" + android:title="@string/preferences_text_scale" /> + --> + <!-- </PreferenceCategory> --> + + <PreferenceCategory android:title="@string/preferences_theme_title"> + <ListPreference + android:defaultValue="@string/preferences_map_theme_default" + android:entries="@array/preferences_map_theme_values" + android:entryValues="@array/preferences_map_theme_keys" + android:key="theme" + android:summary="@string/preferences_theme_summary" + android:title="@string/preferences_theme" /> + </PreferenceCategory> + + <PreferenceCategory android:title="User Interface"> + <CheckBoxPreference + android:title="Distance Touch" + android:summary="Show Route between two Points" + android:key="distanceTouch" /> + </PreferenceCategory> + + <PreferenceCategory android:title="Cache"> + <org.oscim.app.preferences.CacheSizePreference + android:defaultValue="30" + android:key="cacheSize" + android:max="50" + android:summary="Set tile cache size" + android:title="Cache Size" + oscim_app:min="1" + oscim_app:unitsLeft="" + oscim_app:unitsRight="MB" /> + + <!-- http://stackoverflow.com/questions/5298370/how-to-add-a-button-to-a-preferencescreen-android?rq=1 --> + <Preference + android:key="clear_cache" + android:summary="Delete all cached tiles" + android:title="Clear Cache" /> + </PreferenceCategory> + + <!-- + <PreferenceCategory android:title="@string/preferences_general" > + <CheckBoxPreference + android:key="fullscreen" + android:summary="@string/preferences_fullscreen_desc" + android:title="@string/preferences_fullscreen" /> + <CheckBoxPreference + android:key="fixOrientation" + android:summary="@string/preferences_fix_orientation" + android:title="@string/preferences_fix_orientation_desc" /> + <CheckBoxPreference + android:key="wakeLock" + android:summary="@string/preferences_wake_lock_desc" + android:title="@string/preferences_wake_lock" /> + <CheckBoxPreference android:title="@string/preferences_cache_persistence" android:summary="@string/preferences_cache_persistence_desc" + android:key="cachePersistence" /> + <de.sfb.pampa.preferences.CacheSizePreference + android:title="@string/preferences_cache_size" android:summary="@string/preferences_cache_size_desc" android:key="cacheSize" /> + <de.sfb.pampa.preferences.MoveSpeedPreference + android:title="@string/preferences_move_speed" android:summary="@string/preferences_move_speed_desc" android:key="moveSpeed" /> + </PreferenceCategory> + --> + + + <!-- + <PreferenceCategory android:title="@string/preferences_debug" > + + <CheckBoxPreference android:title="@string/preferences_show_fps_counter" android:summary="@string/preferences_show_fps_counter_desc" + android:key="showFpsCounter" /> + <CheckBoxPreference + android:key="drawTileFrames" + android:summary="@string/preferences_show_tile_frames_desc" + android:title="@string/preferences_show_tile_frames" /> + <CheckBoxPreference + android:key="drawUnmatchedWays" + android:summary="@string/preferences_show_unmatched_ways" + android:title="@string/preferences_show_unmatched_ways" /> + <CheckBoxPreference + android:key="disablePolygons" + android:summary="@string/preferences_disable_polygons" + android:title="@string/preferences_disable_polygons" /> + <CheckBoxPreference + android:key="debugLabels" + android:summary="@string/preferences_debug_labels" + android:title="@string/preferences_debug_labels" /> + </PreferenceCategory> + --> + +</PreferenceScreen> \ No newline at end of file diff --git a/vtm-app/src/android-logger.properties b/vtm-app/src/android-logger.properties new file mode 100644 index 00000000..47f60ed5 --- /dev/null +++ b/vtm-app/src/android-logger.properties @@ -0,0 +1 @@ +root=DEBUG:%logger \ No newline at end of file diff --git a/vtm-app/src/org/oscim/app/App.java b/vtm-app/src/org/oscim/app/App.java new file mode 100644 index 00000000..7f95c9a5 --- /dev/null +++ b/vtm-app/src/org/oscim/app/App.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012 Hannes Janetzek + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.view.Display; +import android.view.Surface; +import android.view.WindowManager; + +import org.oscim.android.MapView; +import org.oscim.map.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class App extends Application { + + public final static Logger log = LoggerFactory.getLogger(App.class); + + public static Map map; + public static MapView view; + public static Resources res; + public static TileMap activity; + + public static POISearch poiSearch; + public static RouteSearch routeSearch; + + @Override + public void onCreate() { + super.onCreate(); + res = getResources(); + } + + public static void lockOrientation(Activity activity) { + Display display = ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + int rotation = display.getRotation(); + int tempOrientation = activity.getResources().getConfiguration().orientation; + int orientation = 0; + switch (tempOrientation) { + case Configuration.ORIENTATION_LANDSCAPE: + if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) + orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + else + orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + break; + case Configuration.ORIENTATION_PORTRAIT: + if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_270) + orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + else + orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; + } + activity.setRequestedOrientation(orientation); + } +} diff --git a/vtm-app/src/org/oscim/app/ConnectionHandler.java b/vtm-app/src/org/oscim/app/ConnectionHandler.java new file mode 100644 index 00000000..5be003ee --- /dev/null +++ b/vtm-app/src/org/oscim/app/ConnectionHandler.java @@ -0,0 +1,31 @@ +package org.oscim.app; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.widget.Toast; + +public class ConnectionHandler extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + ConnectivityManager connectivityManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo(); + // NetworkInfo mobNetInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE ); + if (activeNetInfo != null) { + if (activeNetInfo.isConnected()) { + Toast.makeText(context, "Active Network Type : " + activeNetInfo.getTypeName(), + Toast.LENGTH_SHORT).show(); + //if (App.map != null) + // App.map.redrawMap(); + } + //Toast.makeText( context, "Active Network Type : " + activeNetInfo.getTypeName(), Toast.LENGTH_SHORT ).show(); + } + // if( mobNetInfo != null ) + // { + // Toast.makeText( context, "Mobile Network Type : " + mobNetInfo.getTypeName(), Toast.LENGTH_SHORT ).show(); + // } + } +} diff --git a/vtm-app/src/org/oscim/app/FileUtils.java b/vtm-app/src/org/oscim/app/FileUtils.java new file mode 100644 index 00000000..f541cab5 --- /dev/null +++ b/vtm-app/src/org/oscim/app/FileUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app; + +import android.content.res.Resources; + +import java.text.DecimalFormat; + +final class FileUtils { + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.00 "); + private static final double ONE_GIGABYTE = 1000000000; + private static final double ONE_KILOBYTE = 1000; + private static final double ONE_MEGABYTE = 1000000; + + /** + * Formats the given file size as a human readable string, using SI + * prefixes. + * + * @param fileSize the file size to be formatted. + * @param resources a reference to the application resources. + * @return a human readable file size. + * @throws IllegalArgumentException if the given file size is negative. + */ + static String formatFileSize(long fileSize, Resources resources) { + if (fileSize < 0) { + throw new IllegalArgumentException("invalid file size: " + fileSize); + } else if (fileSize < 1000) { + if (fileSize == 1) { + // singular + return "1 " + resources.getString(R.string.file_size_byte); + } + + // plural, including zero + return fileSize + " " + resources.getString(R.string.file_size_bytes); + } else { + if (fileSize < ONE_MEGABYTE) { + return DECIMAL_FORMAT.format(fileSize / ONE_KILOBYTE) + + resources.getString(R.string.file_size_kb); + } else if (fileSize < ONE_GIGABYTE) { + return DECIMAL_FORMAT.format(fileSize / ONE_MEGABYTE) + + resources.getString(R.string.file_size_mb); + } + return DECIMAL_FORMAT.format(fileSize / ONE_GIGABYTE) + + resources.getString(R.string.file_size_gb); + } + } + + private FileUtils() { + throw new IllegalStateException(); + } +} diff --git a/vtm-app/src/org/oscim/app/InfoView.java b/vtm-app/src/org/oscim/app/InfoView.java new file mode 100644 index 00000000..fccb65fd --- /dev/null +++ b/vtm-app/src/org/oscim/app/InfoView.java @@ -0,0 +1,47 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app; + +import android.app.Activity; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.view.WindowManager; +import android.webkit.WebView; + +/** + * Simple activity to display the info web page from the assets folder. + */ +public class InfoView extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WebView webView = new WebView(this); + webView.loadUrl("file:///android_asset/info.xml"); + setContentView(webView); + } + + @Override + protected void onResume() { + super.onResume(); + // check if the full screen mode should be activated + if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("fullscreen", false)) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + } + } +} diff --git a/vtm-app/src/org/oscim/app/MapActivity.java b/vtm-app/src/org/oscim/app/MapActivity.java new file mode 100644 index 00000000..4ac879b2 --- /dev/null +++ b/vtm-app/src/org/oscim/app/MapActivity.java @@ -0,0 +1,125 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * Copyright 2013 Hannes Janetzek + * + * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; + +import org.oscim.android.MapView; +import org.oscim.core.GeoPoint; +import org.oscim.core.MapPosition; +import org.oscim.map.Map; + +/** + * MapActivity is the abstract base class which can be extended in order to use + * a {@link MapView}. There are no abstract methods in this implementation which + * subclasses need to override and no API key or registration is required. + * <p/> + * A subclass may create a MapView either via one of the MapView constructors or + * by inflating an XML layout file. + * <p/> + * When the MapActivity is shut down, the current center position, zoom level + * and map file of the MapView are saved in a preferences file and restored in + * the next startup process. + */ +public abstract class MapActivity extends Activity { + private static final String KEY_LATITUDE = "latitude"; + private static final String KEY_LONGITUDE = "longitude"; + private static final String KEY_MAP_SCALE = "map_scale"; + + private static final String PREFERENCES_FILE = "MapActivity"; + + private static boolean containsViewport(SharedPreferences sharedPreferences) { + return sharedPreferences.contains(KEY_LATITUDE) + && sharedPreferences.contains(KEY_LONGITUDE) + && sharedPreferences.contains(KEY_MAP_SCALE); + } + + protected Map mMap; + protected MapView mMapView; + + public Map map() { + return mMap; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mMap.destroy(); + } + + @Override + protected void onPause() { + super.onPause(); + + Editor editor = getSharedPreferences(PREFERENCES_FILE, MODE_PRIVATE).edit(); + editor.clear(); + + // save the map position + MapPosition mapPosition = new MapPosition(); + + mMap.viewport().getMapPosition(mapPosition); + + GeoPoint geoPoint = mapPosition.getGeoPoint(); + + editor.putInt(KEY_LATITUDE, geoPoint.latitudeE6); + editor.putInt(KEY_LONGITUDE, geoPoint.longitudeE6); + editor.putFloat(KEY_MAP_SCALE, (float) mapPosition.scale); + + editor.commit(); + } + + @Override + protected void onResume() { + super.onResume(); + mMapView.onResume(); + } + + @Override + protected void onStop() { + super.onStop(); + mMapView.onPause(); + } + + /** + * This method is called once by each MapView during its setup process. + * + * @param mapView the calling MapView. + */ + public final void registerMapView(MapView mapView) { + mMapView = mapView; + mMap = mapView.map(); + + SharedPreferences sharedPreferences = getSharedPreferences(PREFERENCES_FILE, + MODE_PRIVATE); + + if (containsViewport(sharedPreferences)) { + // get and set the map position and zoom level + int latitudeE6 = sharedPreferences.getInt(KEY_LATITUDE, 0); + int longitudeE6 = sharedPreferences.getInt(KEY_LONGITUDE, 0); + float scale = sharedPreferences.getFloat(KEY_MAP_SCALE, 1); + + MapPosition mapPosition = new MapPosition(); + mapPosition.setPosition(latitudeE6 / 1E6, longitudeE6 / 1E6); + mapPosition.setScale(scale); + + mMap.setMapPosition(mapPosition); + } + } +} diff --git a/vtm-app/src/org/oscim/app/MapLayers.java b/vtm-app/src/org/oscim/app/MapLayers.java new file mode 100644 index 00000000..0af4fd8f --- /dev/null +++ b/vtm-app/src/org/oscim/app/MapLayers.java @@ -0,0 +1,189 @@ +package org.oscim.app; + +import android.content.SharedPreferences; + +import org.oscim.android.cache.TileCache; +import org.oscim.layers.GenericLayer; +import org.oscim.layers.Layer; +import org.oscim.layers.TileGridLayer; +import org.oscim.layers.tile.bitmap.BitmapTileLayer; +import org.oscim.layers.tile.buildings.BuildingLayer; +import org.oscim.layers.tile.vector.VectorTileLayer; +import org.oscim.layers.tile.vector.labeling.LabelLayer; +import org.oscim.theme.ThemeFile; +import org.oscim.theme.VtmThemes; +import org.oscim.tiling.ITileCache; +import org.oscim.tiling.TileSource; +import org.oscim.tiling.source.UrlTileSource; +import org.oscim.tiling.source.bitmap.DefaultSources; +import org.oscim.tiling.source.mapfile.MapFileTileSource; +import org.oscim.tiling.source.mapnik.MapnikVectorTileSource; +import org.oscim.tiling.source.oscimap4.OSciMap4TileSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MapLayers { + + final static Logger log = LoggerFactory.getLogger(MapLayers.class); + + private static final String CACHE_DIRECTORY = "/Android/data/org.oscim.app/cache/"; + + abstract static class Config { + final String name; + + public Config(String name) { + this.name = name; + } + + abstract TileSource init(); + } + + static Config[] configs = new Config[]{new Config("OPENSCIENCEMAP4") { + TileSource init() { + return new OSciMap4TileSource(); + } + }, new Config("MAPSFORGE") { + TileSource init() { + return new MapFileTileSource().setOption("file", + "/storage/sdcard0/germany.map"); + } + }, new Config("MAPNIK_VECTOR") { + TileSource init() { + return new MapnikVectorTileSource(); + } + }}; + + private VectorTileLayer mBaseLayer; + private String mMapDatabase; + private ITileCache mCache; + + private GenericLayer mGridOverlay; + private boolean mGridEnabled; + + // FIXME -> implement LayerGroup + private int mBackgroundId = -2; + private Layer mBackroundPlaceholder; + private Layer mBackgroundLayer; + + public MapLayers() { + mBackroundPlaceholder = new Layer(null) { + }; + setBackgroundMap(-1); + } + + void setBaseMap(SharedPreferences preferences) { + String dbname = preferences.getString("mapDatabase", "OPENSCIENCEMAP4"); + + if (dbname.equals(mMapDatabase) && mBaseLayer != null) + return; + + TileSource tileSource = null; + for (Config c : configs) + if (c.name.equals(dbname)) + tileSource = c.init(); + + if (tileSource == null) { + tileSource = configs[0].init(); + dbname = configs[0].name; + preferences.edit().putString("mapDatabase", dbname).commit(); + } + + if (tileSource instanceof UrlTileSource) { + mCache = new TileCache(App.activity, CACHE_DIRECTORY, dbname); + mCache.setCacheSize(512 * (1 << 10)); + tileSource.setCache(mCache); + } else { + mCache = null; + } + + if (mBaseLayer == null) { + mBaseLayer = App.map.setBaseMap(tileSource); + App.map.layers().add(2, new BuildingLayer(App.map, mBaseLayer)); + App.map.layers().add(3, new LabelLayer(App.map, mBaseLayer)); + } else + mBaseLayer.setTileSource(tileSource); + + mMapDatabase = dbname; + } + + void setPreferences(SharedPreferences preferences) { + setBaseMap(preferences); + + ThemeFile theme = VtmThemes.DEFAULT; + if (preferences.contains("theme")) { + String name = preferences.getString("theme", "DEFAULT"); + try { + theme = VtmThemes.valueOf(name); + } catch (IllegalArgumentException e) { + theme = VtmThemes.DEFAULT; + } + } + + App.map.setTheme(theme); + + // default cache size 20MB + int cacheSize = preferences.getInt("cacheSize", 20); + + if (mCache != null) + mCache.setCacheSize(cacheSize * (1 << 20)); + + } + + void enableGridOverlay(boolean enable) { + if (mGridEnabled == enable) + return; + + if (enable) { + if (mGridOverlay == null) + mGridOverlay = new TileGridLayer(App.map); + + App.map.layers().add(mGridOverlay); + } else { + App.map.layers().remove(mGridOverlay); + } + + mGridEnabled = enable; + App.map.updateMap(true); + } + + boolean isGridEnabled() { + return mGridEnabled; + } + + void setBackgroundMap(int id) { + if (id == mBackgroundId) + return; + + App.map.layers().remove(mBackgroundLayer); + mBackgroundLayer = null; + + switch (id) { + case R.id.menu_layer_openstreetmap: + mBackgroundLayer = new BitmapTileLayer(App.map, DefaultSources.OPENSTREETMAP.build()); + break; + + case R.id.menu_layer_naturalearth: + mBackgroundLayer = new BitmapTileLayer(App.map, DefaultSources.NE_LANDCOVER.build()); + break; + default: + mBackgroundLayer = mBackroundPlaceholder; + id = -1; + } + + if (mBackgroundLayer instanceof BitmapTileLayer) + App.map.setBaseMap((BitmapTileLayer) mBackgroundLayer); + else + App.map.layers().add(1, mBackroundPlaceholder); + + mBackgroundId = id; + } + + int getBackgroundId() { + return mBackgroundId; + } + + public void deleteCache() { + if (mCache != null) + mCache.setCacheSize(0); + } +} diff --git a/vtm-app/src/org/oscim/app/POIActivity.java b/vtm-app/src/org/oscim/app/POIActivity.java new file mode 100644 index 00000000..930f3f15 --- /dev/null +++ b/vtm-app/src/org/oscim/app/POIActivity.java @@ -0,0 +1,309 @@ +/* + * Copyright 2012 osmdroidbonuspack: M.Kergall + * Copyright 2012 Hannes Janetzek + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.oscim.app; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.database.DataSetObserver; +import android.net.Uri; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.BaseAdapter; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import org.osmdroid.location.FourSquareProvider; +import org.osmdroid.location.POI; + +import java.util.List; + +/** + * Activity showing POIs as a list. + * + * @author M.Kergall + */ + +// TODO implement: +// http://codehenge.net/blog/2011/06/android-development-tutorial- +// asynchronous-lazy-loading-and-caching-of-listview-images/ + +public class POIActivity extends Activity { + + AutoCompleteTextView poiTagText; + POIAdapter mAdapter; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.items_list); + + ListView list = (ListView) findViewById(R.id.items); + + Intent myIntent = getIntent(); + + final List<POI> pois = App.poiSearch.getPOIs(); + final int currentNodeId = myIntent.getIntExtra("ID", -1); + POIAdapter adapter = new POIAdapter(this, pois); + mAdapter = adapter; + + list.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> arg0, View view, int position, long index) { + //log.debug("poi on click: " + position); + Intent intent = new Intent(); + intent.putExtra("ID", position); + setResult(RESULT_OK, intent); + finish(); + } + }); + + list.setAdapter(adapter); + list.setSelection(currentNodeId); + + // POI search interface: + String[] poiTags = getResources().getStringArray(R.array.poi_tags); + poiTagText = (AutoCompleteTextView) findViewById(R.id.poiTag); + ArrayAdapter<String> textadapter = new ArrayAdapter<String>(this, + android.R.layout.simple_dropdown_item_1line, + poiTags); + poiTagText.setAdapter(textadapter); + + // Button setPOITagButton = (Button) findViewById(R.id.buttonSetPOITag); + // setPOITagButton.setOnClickListener(new View.OnClickListener() { + // @Override + // public void onClick(View v) { + // hideKeyboard(); + // //Start search: + // App.poiSearch.getPOIAsync(poiTagText.getText().toString()); + // } + // }); + + // FIXME! + Button btn = (Button) findViewById(R.id.pois_btn_flickr); + btn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + hideKeyboard(); + App.poiSearch.getPOIAsync(POISearch.TAG_FLICKR); + } + }); + + btn = (Button) findViewById(R.id.pois_btn_nominatim); + btn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + hideKeyboard(); + String text = poiTagText.getText().toString(); + if (text == null || text.length() == 0) + App.poiSearch.getPOIAsync("bremen"); + else + App.poiSearch.getPOIAsync(text); + } + }); + + btn = (Button) findViewById(R.id.pois_btn_wikipedia); + btn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + hideKeyboard(); + App.poiSearch.getPOIAsync(POISearch.TAG_WIKIPEDIA); + } + }); + + btn = (Button) findViewById(R.id.pois_btn_foursquare); + btn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + hideKeyboard(); + App.poiSearch.getPOIAsync(POISearch.TAG_FOURSQUARE + + poiTagText.getText().toString()); + } + }); + + registerForContextMenu(list); + + // only show keyboard when nothing in the list yet + if (pois == null || pois.size() == 0) { + poiTagText.postDelayed(new Runnable() { + @Override + public void run() { + InputMethodManager keyboard = (InputMethodManager) + getSystemService(Context.INPUT_METHOD_SERVICE); + keyboard.showSoftInput(poiTagText, 0); + } + }, 200); + } + } + + private void hideKeyboard() { + InputMethodManager imm = (InputMethodManager) + getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(poiTagText.getWindowToken(), 0); + } + + @Override + protected void onNewIntent(Intent intent) { + // from SearchableDictionary Example: + // Because this activity has set launchMode="singleTop", the system calls this method + // to deliver the intent if this activity is currently the foreground activity when + // invoked again (when the user executes a search from this activity, we don't create + // a new instance of this activity, so the system delivers the search intent here) + // handleIntent(intent); + + // final ArrayList<POI> pois = intent.getParcelableArrayListExtra("POI"); + // final int currentNodeId = intent.getIntExtra("ID", -1); + // POIAdapter adapter = new POIAdapter(this, pois); + // mAdapter.setPOI(pois); + mAdapter.notifyDataSetChanged(); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + if (v.getId() == R.id.items) { + //AdapterView.AdapterContextMenuInfo info = + // (AdapterView.AdapterContextMenuInfo) menuInfo; + //log.debug("list context menu created " + info.position); + + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.poi_menu, menu); + + } + + super.onCreateContextMenu(menu, v, menuInfo); + + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + //log.debug("context menu item selected " + item.getItemId()); + + if (item.getItemId() == R.id.menu_link) { + + AdapterView.AdapterContextMenuInfo info = + (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); + + POI poi = (POI) mAdapter.getItem(info.position); + if (poi == null || poi.url == null) + return false; + + if (poi.serviceId == POI.POI_SERVICE_4SQUARE) { + FourSquareProvider.browse(this, poi); + return true; + } else { + Intent i = new Intent(Intent.ACTION_VIEW); + i.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + i.setData(Uri.parse(poi.url)); + startActivity(i); + + } + return true; + + } + + return super.onContextItemSelected(item); + } + + class POIObserver extends DataSetObserver { + @Override + public void onChanged() { + mAdapter.notifyDataSetChanged(); + } + } +} + +class POIAdapter extends BaseAdapter implements OnClickListener { + private Context mContext; + private final List<POI> mPois; + + public POIAdapter(Context context, List<POI> pois) { + mContext = context; + mPois = pois; + } + + @Override + public int getCount() { + if (mPois == null) + return 0; + + return mPois.size(); + } + + @Override + public Object getItem(int position) { + if (mPois == null) + return null; + + return mPois.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View view, ViewGroup viewGroup) { + POI entry = (POI) getItem(position); + if (view == null) { + LayoutInflater inflater = (LayoutInflater) mContext + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = inflater.inflate(R.layout.item_layout, null); + + ViewHolder holder = new ViewHolder(); + holder.title = (TextView) view.findViewById(R.id.title); + holder.details = (TextView) view.findViewById(R.id.details); + holder.thumbnail = (ImageView) view.findViewById(R.id.thumbnail); + view.setTag(holder); + } + + ViewHolder holder = (ViewHolder) view.getTag(); + + holder.title.setText((entry.url == null ? "" : "[link] ") + entry.type); + holder.details.setText(entry.description); + + entry.fetchThumbnail(holder.thumbnail); + + return view; + } + + @Override + public void onClick(View arg0) { + //nothing to do. + } + + class ViewHolder { + public TextView title; + public TextView details; + public ImageView thumbnail; + } +} diff --git a/vtm-app/src/org/oscim/app/POISearch.java b/vtm-app/src/org/oscim/app/POISearch.java new file mode 100644 index 00000000..b79c4750 --- /dev/null +++ b/vtm-app/src/org/oscim/app/POISearch.java @@ -0,0 +1,320 @@ +/* + * Copyright 2012 osmdroid: M.Kergall + * Copyright 2012 Hannes Janetzek + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app; + +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.Toast; + +import org.oscim.android.canvas.AndroidGraphics; +import org.oscim.core.BoundingBox; +import org.oscim.core.GeoPoint; +import org.oscim.layers.marker.MarkerItem.HotspotPlace; +import org.oscim.layers.marker.MarkerSymbol; +import org.oscim.map.Map; +import org.osmdroid.location.FlickrPOIProvider; +import org.osmdroid.location.FourSquareProvider; +import org.osmdroid.location.GeoNamesPOIProvider; +import org.osmdroid.location.NominatimPOIProvider; +import org.osmdroid.location.POI; +import org.osmdroid.location.PicasaPOIProvider; +import org.osmdroid.overlays.DefaultInfoWindow; +import org.osmdroid.overlays.ExtendedMarkerItem; +import org.osmdroid.overlays.ItemizedOverlayWithBubble; + +import java.util.ArrayList; +import java.util.List; + +public class POISearch { + private final ArrayList<POI> mPOIs; + ItemizedOverlayWithBubble<ExtendedMarkerItem> poiMarkers; + MarkerSymbol[] mMarkers; + + private final static int MDEFAULT = 0; + private final static int MFLICKR = 1; + private final static int MPICASA = 2; + private final static int MWIKI16 = 3; + private final static int MWIKI32 = 4; + + POISearch() { + mPOIs = new ArrayList<POI>(); + //POI markers: + final ArrayList<ExtendedMarkerItem> poiItems = new ArrayList<ExtendedMarkerItem>(); + + poiMarkers = new ItemizedOverlayWithBubble<ExtendedMarkerItem>(App.map, + App.activity, + null, + poiItems, + new POIInfoWindow(App.map)); + + App.map.layers().add(poiMarkers); + + mMarkers = new MarkerSymbol[5]; + + mMarkers[MDEFAULT] = AndroidGraphics + .makeMarker(App.res.getDrawable(R.drawable.pin), HotspotPlace.BOTTOM_CENTER); + + mMarkers[MFLICKR] = AndroidGraphics + .makeMarker(App.res.getDrawable(R.drawable.marker_poi_flickr), null); + + mMarkers[MPICASA] = AndroidGraphics + .makeMarker(App.res.getDrawable(R.drawable.marker_poi_picasa_24), null); + + mMarkers[MWIKI16] = AndroidGraphics + .makeMarker(App.res.getDrawable(R.drawable.marker_poi_wikipedia_16), null); + + mMarkers[MWIKI32] = AndroidGraphics + .makeMarker(App.res.getDrawable(R.drawable.marker_poi_wikipedia_32), null); + } + + public List<POI> getPOIs() { + return mPOIs; + } + + final static String TAG_WIKIPEDIA = "wikipedia"; + final static String TAG_FLICKR = "flickr"; + final static String TAG_PICASA = "picasa"; + final static String TAG_FOURSQUARE = "foursquare"; + + //private final static String TAG_NOMINATIM = "nominatim"; + + class POITask extends AsyncTask<Object, Void, List<POI>> { + String mTag; + + @Override + protected List<POI> doInBackground(Object... params) { + mTag = (String) params[0]; + + if (mTag == null || mTag.equals("")) { + return null; + } + BoundingBox bb = App.map.getBoundingBox(0); + + if (mTag.equals(TAG_WIKIPEDIA)) { + GeoNamesPOIProvider poiProvider = new GeoNamesPOIProvider("mkergall"); + //ArrayList<POI> pois = poiProvider.getPOICloseTo(point, 30, 20.0); + //Get POI inside the bounding box of the current map view: + + return poiProvider.getPOIInside(bb, 30); + + //OverpassPOIProvider poiProvider = new OverpassPOIProvider(); + //return poiProvider.getPOIInside(bb, "", 0); + } else if (mTag.equals(TAG_FLICKR)) { + FlickrPOIProvider poiProvider = new FlickrPOIProvider("c39be46304a6c6efda8bc066c185cd7e"); + return poiProvider.getPOIInside(bb, null, 20); + } else if (mTag.startsWith(TAG_PICASA)) { + PicasaPOIProvider poiProvider = new PicasaPOIProvider(null); + String q = mTag.substring(7); + return poiProvider.getPOIInside(bb, q, 20); + } else if (mTag.startsWith(TAG_FOURSQUARE)) { + FourSquareProvider poiProvider = new FourSquareProvider(null, null); + String q = mTag.substring(10); + // String q = mTag.substring("picasa".length()); + return poiProvider.getPOIInside(bb, q, 40); + } else { + NominatimPOIProvider poiProvider = new NominatimPOIProvider(); + //poiProvider.setService(NominatimPOIProvider.NOMINATIM_POI_SERVICE); + + poiProvider.setService(NominatimPOIProvider.MAPQUEST_POI_SERVICE); + + //pois = poiProvider.getPOIAlong(mRoute.getRouteLow(), mTag, 100, 2.0); + return poiProvider.getPOIInside(bb, mTag, 10); + } + } + + @Override + protected void onPostExecute(List<POI> pois) { + if (mTag.equals("")) { + //no search, no message + } else if (pois == null) { + Toast.makeText(App.activity, + "Technical issue when getting " + mTag + " POI.", + Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(App.activity, + pois.size() + " " + mTag + " entries found", + Toast.LENGTH_SHORT).show(); + + // if (mTag.equals("flickr") || mTag.startsWith("picasa") || mTag.equals("wikipedia")) + // startAsyncThumbnailsLoading(mPOIs); + } + + updateUIWithPOI(pois); + } + } + + void updateUIWithPOI(List<POI> pois) { + mPOIs.clear(); + if (pois == null) { + showPOIActivity(true); + App.map.updateMap(true); + return; + } + + mPOIs.addAll(pois); + + for (POI poi : pois) { + String desc = null; + String name = null; + + if (poi.serviceId == POI.POI_SERVICE_NOMINATIM) { + name = poi.description; + String[] split = name.split(", "); + if (split != null && split.length > 1) { + name = split[0]; + desc = split[1]; + + for (int i = 2; i < 3 && i < split.length; i++) + desc += "," + split[i]; + } + + } else { + desc = poi.description; + } + + ExtendedMarkerItem poiMarker = + new ExtendedMarkerItem(poi.type + (name == null ? "" : ": " + name), desc, + poi.location); + MarkerSymbol marker = null; + + if (poi.serviceId == POI.POI_SERVICE_NOMINATIM) { + + marker = mMarkers[MDEFAULT]; + } else if (poi.serviceId == POI.POI_SERVICE_GEONAMES_WIKIPEDIA) { + if (poi.rank < 90) + marker = mMarkers[MWIKI16]; + else + marker = mMarkers[MWIKI32]; + } else if (poi.serviceId == POI.POI_SERVICE_FLICKR) { + marker = mMarkers[MFLICKR]; + } else if (poi.serviceId == POI.POI_SERVICE_PICASA) { + marker = mMarkers[MPICASA]; + poiMarker.setSubDescription(poi.category); + } else if (poi.serviceId == POI.POI_SERVICE_4SQUARE) { + marker = mMarkers[MDEFAULT]; + poiMarker.setSubDescription(poi.category); + } + + poiMarker.setMarker(marker); + //thumbnail loading moved in POIInfoWindow.onOpen for better performances. + poiMarker.setRelatedObject(poi); + poiMarkers.addItem(poiMarker); + } + + showPOIActivity(true); + App.map.updateMap(true); + } + + private void showPOIActivity(boolean setNew) { + // show or update + Intent intent = new Intent(App.activity, POIActivity.class); + intent.putExtra("ID", poiMarkers.getBubbledItemId()); + if (setNew) + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); + App.activity.startActivityForResult(intent, TileMap.POIS_REQUEST); + } + + void getPOIAsync(String tag) { + poiMarkers.removeAllItems(); + new POITask().execute(tag); + } + + class POIInfoWindow extends DefaultInfoWindow { + + private Button mButton; + private ImageView mImage; + + public POIInfoWindow(Map map) { + super(R.layout.bonuspack_bubble, App.view); + + mButton = (Button) mView.findViewById(R.id.bubble_moreinfo); + mImage = (ImageView) mView.findViewById(R.id.bubble_image); + + //bonuspack_bubble layouts already contain a "more info" button. + mButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + POI poi = (POI) view.getTag(); + + if (poi == null) + return; + + if (poi.serviceId == POI.POI_SERVICE_4SQUARE) { + FourSquareProvider.browse(view.getContext(), poi); + } else if (poi.url != null) { + Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(poi.url)); + i.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + view.getContext().startActivity(i); + } + } + }); + + getView().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + POI poi = (POI) view.getTag(); + + if (poi != null) + showPOIActivity(false); + } + }); + } + + @Override + public void onOpen(ExtendedMarkerItem item) { + POI poi = (POI) item.getRelatedObject(); + + super.onOpen(item); + + poi.fetchThumbnail(mImage); + + //Show or hide "more info" button: + if (poi.url != null) + mButton.setVisibility(View.VISIBLE); + else + mButton.setVisibility(View.GONE); + + mButton.setTag(poi); + getView().setTag(poi); + } + } + + public boolean onContextItemSelected(MenuItem item, GeoPoint geoPoint) { + switch (item.getItemId()) { + case R.id.menu_poi_nearby: + Intent intent = new Intent(App.activity, POIActivity.class); + intent.putExtra("ID", poiMarkers.getBubbledItemId()); + App.activity.startActivityForResult(intent, TileMap.POIS_REQUEST); + return true; + + case R.id.menu_poi_clear: + poiMarkers.removeAllItems(); + mPOIs.clear(); + App.map.updateMap(true); + + return true; + default: + } + return false; + + } + +} diff --git a/vtm-app/src/org/oscim/app/RouteSearch.java b/vtm-app/src/org/oscim/app/RouteSearch.java new file mode 100644 index 00000000..667f9e4d --- /dev/null +++ b/vtm-app/src/org/oscim/app/RouteSearch.java @@ -0,0 +1,452 @@ +/* + * Copyright 2012 osmdroid: M.Kergall + * Copyright 2012 Hannes Janetzek + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app; + +import android.app.Activity; +import android.location.Address; +import android.os.AsyncTask; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import org.oscim.android.canvas.AndroidGraphics; +import org.oscim.core.GeoPoint; +import org.oscim.layers.PathLayer; +import org.oscim.layers.marker.MarkerItem.HotspotPlace; +import org.oscim.layers.marker.MarkerSymbol; +import org.osmdroid.location.GeocoderNominatim; +import org.osmdroid.overlays.DefaultInfoWindow; +import org.osmdroid.overlays.ExtendedMarkerItem; +import org.osmdroid.overlays.ItemizedOverlayWithBubble; +import org.osmdroid.routing.Route; +import org.osmdroid.routing.RouteProvider; +import org.osmdroid.routing.provider.OSRMRouteProvider; + +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +public class RouteSearch { + private static int START_INDEX = -2, DEST_INDEX = -1; + + private final PathLayer mRouteOverlay; + //private final ItemizedOverlayWithBubble<ExtendedOverlayItem> mRouteMarkers; + private final ItemizedOverlayWithBubble<ExtendedMarkerItem> mItineraryMarkers; + + private final RouteBar mRouteBar; + + private GeoPoint mStartPoint, mDestinationPoint; + private final ArrayList<GeoPoint> mViaPoints; + + private ExtendedMarkerItem markerStart, markerDestination; + + private UpdateRouteTask mRouteTask; + + RouteSearch() { + mViaPoints = new ArrayList<GeoPoint>(); + + // Itinerary markers: + ArrayList<ExtendedMarkerItem> waypointsItems = new ArrayList<ExtendedMarkerItem>(); + + mItineraryMarkers = new ItemizedOverlayWithBubble<ExtendedMarkerItem>(App.map, + App.activity, + null, + waypointsItems, + new ViaPointInfoWindow(R.layout.itinerary_bubble)); + + //updateIternaryMarkers(); + + //Route and Directions + //ArrayList<ExtendedOverlayItem> routeItems = new ArrayList<ExtendedOverlayItem>(); + //mRouteMarkers = new ItemizedOverlayWithBubble<ExtendedOverlayItem>(App.map, App.activity, + // null, routeItems); + + mRouteOverlay = new PathLayer(App.map, 0xAA0000FF, 3); + + // TODO use LayerGroup + App.map.layers().add(mRouteOverlay); + //App.map.getOverlays().add(mRouteMarkers); + App.map.layers().add(mItineraryMarkers); + + mRouteBar = new RouteBar(App.activity); + } + + /** + * Retrieve route between p1 and p2 and update overlays. + */ + public void showRoute(GeoPoint p1, GeoPoint p2) { + clearOverlays(); + + mStartPoint = p1; + markerStart = putMarkerItem(markerStart, mStartPoint, START_INDEX, + R.string.departure, R.drawable.marker_departure, -1); + + mDestinationPoint = p2; + markerDestination = putMarkerItem(markerDestination, mDestinationPoint, DEST_INDEX, + R.string.destination, + R.drawable.marker_destination, -1); + + getRouteAsync(); + } + + /** + * Reverse Geocoding + */ + public String getAddress(GeoPoint p) { + GeocoderNominatim geocoder = new GeocoderNominatim(App.activity); + String theAddress; + try { + double dLatitude = p.getLatitude(); + double dLongitude = p.getLongitude(); + List<Address> addresses = geocoder.getFromLocation(dLatitude, dLongitude, 1); + StringBuilder sb = new StringBuilder(); + if (addresses.size() > 0) { + Address address = addresses.get(0); + int n = address.getMaxAddressLineIndex(); + for (int i = 0; i <= n; i++) { + if (i != 0) + sb.append(", "); + sb.append(address.getAddressLine(i)); + } + theAddress = new String(sb.toString()); + } else { + theAddress = null; + } + } catch (IOException e) { + theAddress = null; + } + if (theAddress != null) { + return theAddress; + } + return ""; + } + + // Async task to reverse-geocode the marker position in a separate thread: + class GeocodingTask extends AsyncTask<Object, Void, String> { + ExtendedMarkerItem marker; + + @Override + protected String doInBackground(Object... params) { + marker = (ExtendedMarkerItem) params[0]; + return getAddress(marker.getPoint()); + } + + @Override + protected void onPostExecute(String result) { + marker.setDescription(result); + } + } + + /* add (or replace) an item in markerOverlays. p position. */ + public ExtendedMarkerItem putMarkerItem(ExtendedMarkerItem item, GeoPoint p, int index, + int titleResId, int markerResId, int iconResId) { + + if (item != null) + mItineraryMarkers.removeItem(item); + + MarkerSymbol marker = AndroidGraphics.makeMarker(App.res.getDrawable(markerResId), + HotspotPlace.BOTTOM_CENTER); + + ExtendedMarkerItem overlayItem = + new ExtendedMarkerItem(App.res.getString(titleResId), "", p); + + overlayItem.setMarker(marker); + + if (iconResId != -1) + overlayItem.setImage(App.res.getDrawable(iconResId)); + + overlayItem.setRelatedObject(Integer.valueOf(index)); + + mItineraryMarkers.addItem(overlayItem); + + App.map.updateMap(true); + + //Start geocoding task to update the description of the marker with its address: + new GeocodingTask().execute(overlayItem); + + return overlayItem; + } + + public void addViaPoint(GeoPoint p) { + mViaPoints.add(p); + putMarkerItem(null, p, mViaPoints.size() - 1, + R.string.viapoint, R.drawable.marker_via, -1); + } + + public void removePoint(int index) { + if (index == START_INDEX) { + mStartPoint = null; + } else if (index == DEST_INDEX) { + mDestinationPoint = null; + } else + mViaPoints.remove(index); + + getRouteAsync(); + updateIternaryMarkers(); + } + + public void updateIternaryMarkers() { + mItineraryMarkers.removeAllItems(); + + //Start marker: + if (mStartPoint != null) { + markerStart = putMarkerItem(null, mStartPoint, START_INDEX, + R.string.departure, R.drawable.marker_departure, -1); + } + //Via-points markers if any: + for (int index = 0; index < mViaPoints.size(); index++) { + putMarkerItem(null, mViaPoints.get(index), index, + R.string.viapoint, R.drawable.marker_via, -1); + } + //Destination marker if any: + if (mDestinationPoint != null) { + markerDestination = putMarkerItem(null, mDestinationPoint, DEST_INDEX, + R.string.destination, + R.drawable.marker_destination, -1); + } + } + + //------------ Route and Directions + private void updateOverlays(Route route) { + //mRouteMarkers.removeAllItems(); + + mRouteOverlay.clearPath(); + + if (route == null || route.status == Route.STATUS_DEFAULT) { + App.activity.showToastOnUiThread(App.res.getString(R.string.route_lookup_error)); + return; + } + + mRouteOverlay.setPoints(route.routeHigh); + + //OverlayMarker marker = AndroidGraphics.makeMarker(App.res, R.drawable.marker_node, null); + + //int n = route.nodes.size(); + //for (int i = 0; i < n; i++) { + // RouteNode node = route.nodes.get(i); + // String instructions = (node.instructions == null ? "" : node.instructions); + // ExtendedOverlayItem nodeMarker = new ExtendedOverlayItem( + // "Step " + (i + 1), instructions, node.location); + // + // nodeMarker.setSubDescription(route.getLengthDurationText(node.length, node.duration)); + // nodeMarker.setMarkerHotspot(OverlayItem.HotspotPlace.CENTER); + // nodeMarker.setMarker(marker); + // + // mRouteMarkers.addItem(nodeMarker); + //} + + App.map.updateMap(true); + } + + void clearOverlays() { + //mRouteMarkers.removeAllItems(true); + mItineraryMarkers.removeAllItems(true); + + mRouteOverlay.clearPath(); + mStartPoint = null; + mDestinationPoint = null; + mViaPoints.clear(); + + App.map.updateMap(true); + } + + /** + * Async task to get the route in a separate thread. + */ + class UpdateRouteTask extends AsyncTask<List<GeoPoint>, Void, Route> { + @Override + protected Route doInBackground(List<GeoPoint>... wp) { + List<GeoPoint> waypoints = wp[0]; + + //RouteProvider routeProvider = new MapQuestRouteProvider(); + //Locale locale = Locale.getDefault(); + //routeProvider.addRequestOption("locale=" + locale.getLanguage() + "_" + // + locale.getCountry()); + //routeProvider.addRequestOption("routeType=pedestrian"); + + //RouteProvider routeProvider = new GoogleRouteProvider(); + RouteProvider routeProvider = new OSRMRouteProvider(); + return routeProvider.getRoute(waypoints); + } + + @Override + protected void onPostExecute(Route result) { + + updateOverlays(result); + mRouteBar.set(result); + + mRouteTask = null; + } + } + + @SuppressWarnings("unchecked") + public void getRouteAsync() { + if (mRouteTask != null) { + mRouteTask.cancel(true); + mRouteTask = null; + } + + if (mStartPoint == null || mDestinationPoint == null) { + mRouteOverlay.clearPath(); + return; + } + + List<GeoPoint> waypoints = new ArrayList<GeoPoint>(); + waypoints.add(mStartPoint); + //add intermediate via points: + for (GeoPoint p : mViaPoints) { + waypoints.add(p); + } + waypoints.add(mDestinationPoint); + + mRouteTask = new UpdateRouteTask(); + mRouteTask.execute(waypoints); + } + + boolean onContextItemSelected(MenuItem item, GeoPoint geoPoint) { + switch (item.getItemId()) { + case R.id.menu_route_departure: + mStartPoint = geoPoint; + + markerStart = putMarkerItem(markerStart, mStartPoint, START_INDEX, + R.string.departure, R.drawable.marker_departure, -1); + + getRouteAsync(); + return true; + + case R.id.menu_route_destination: + mDestinationPoint = geoPoint; + + markerDestination = putMarkerItem(markerDestination, mDestinationPoint, DEST_INDEX, + R.string.destination, + R.drawable.marker_destination, -1); + + getRouteAsync(); + return true; + + case R.id.menu_route_viapoint: + GeoPoint viaPoint = geoPoint; + addViaPoint(viaPoint); + + getRouteAsync(); + return true; + + case R.id.menu_route_clear: + clearOverlays(); + return true; + + default: + } + return false; + } + + public boolean isEmpty() { + return (mItineraryMarkers.size() == 0); + } + + class ViaPointInfoWindow extends DefaultInfoWindow { + + int mSelectedPoint; + + public ViaPointInfoWindow(int layoutResId) { + super(layoutResId, App.view); + + Button btnDelete = (Button) (mView.findViewById(R.id.bubble_delete)); + btnDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + removePoint(mSelectedPoint); + close(); + } + }); + } + + @Override + public void onOpen(ExtendedMarkerItem item) { + mSelectedPoint = ((Integer) item.getRelatedObject()).intValue(); + super.onOpen(item); + } + } + + class RouteBar { + + TextView mDistance = null; + TextView mRouteLength = null; + TextView mTravelTime = null; + ImageView mClearButton = null; + RelativeLayout mRouteBarView = null; + + RouteBar(Activity activity) { + + mRouteBarView = (RelativeLayout) activity.findViewById(R.id.route_bar); + mDistance = (TextView) activity.findViewById(R.id.route_bar_distance); + mRouteLength = (TextView) activity.findViewById(R.id.route_bar_route_length); + mTravelTime = (TextView) activity.findViewById(R.id.route_bar_travel_time); + + mClearButton = (ImageView) activity.findViewById(R.id.route_bar_clear); + + mRouteBarView.setVisibility(View.INVISIBLE); + + mClearButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mRouteBarView.setVisibility(View.INVISIBLE); + clearOverlays(); + } + }); + } + + public void set(Route result) { + DecimalFormat twoDForm = new DecimalFormat("#.#"); + DecimalFormat oneDForm = new DecimalFormat("#"); + int hour = ((int) result.duration / 3600); + int minute = ((int) result.duration % 3600) / 60; + String time = ""; + if (hour == 0 && minute == 0) { + time = "?"; + } else if (hour == 0 && minute != 0) { + time = minute + "m"; + } else { + time = hour + "h " + minute + "m"; + } + + double dis = ((double) (mStartPoint.distanceTo(mDestinationPoint))) / 1000; + String distance; + String shortpath; + if (dis < 100) { + distance = twoDForm.format(dis); + } else { + distance = oneDForm.format(dis); + } + if (result.length == 0) { + shortpath = "?"; + } else if (result.length < 100) { + shortpath = twoDForm.format(result.length); + } else { + shortpath = oneDForm.format(result.length); + } + + mRouteBarView.setVisibility(View.VISIBLE); + mDistance.setText(distance + " km"); + mTravelTime.setText(time); + mRouteLength.setText(shortpath + " km"); + } + } +} diff --git a/vtm-app/src/org/oscim/app/TileMap.java b/vtm-app/src/org/oscim/app/TileMap.java new file mode 100755 index 00000000..8d2e5a2d --- /dev/null +++ b/vtm-app/src/org/oscim/app/TileMap.java @@ -0,0 +1,518 @@ +/* Copyright 2010, 2011, 2012 mapsforge.org + * Copyright 2012 Hannes Janetzek + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.oscim.app; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Vibrator; +import android.preference.PreferenceManager; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.WindowManager; +import android.widget.Toast; + +import org.oscim.android.MapView; +import org.oscim.app.location.Compass; +import org.oscim.app.location.LocationDialog; +import org.oscim.app.location.LocationHandler; +import org.oscim.app.preferences.EditPreferences; +import org.oscim.core.GeoPoint; +import org.oscim.overlay.DistanceTouchOverlay; +import org.osmdroid.location.POI; +import org.osmdroid.overlays.MapEventsReceiver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +public class TileMap extends MapActivity implements MapEventsReceiver { + final static Logger log = LoggerFactory.getLogger(TileMap.class); + + private static final int DIALOG_ENTER_COORDINATES = 0; + private static final int DIALOG_LOCATION_PROVIDER_DISABLED = 2; + + //private static final int SELECT_RENDER_THEME_FILE = 1; + protected static final int POIS_REQUEST = 2; + + private LocationHandler mLocation; + private Compass mCompass; + + private Menu mMenu = null; + private MapLayers mMapLayers; + + public MapLayers getMapLayers() { + return mMapLayers; + } + + private DistanceTouchOverlay mDistanceTouch; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_tilemap); + App.view = (MapView) findViewById(R.id.mapView); + registerMapView(App.view); + + App.map = mMap; + App.activity = this; + + mMapLayers = new MapLayers(); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + mMapLayers.setBaseMap(prefs); + + if (!prefs.contains("distanceTouch")) + prefs.edit().putBoolean("distanceTouch", true).apply(); + + if (prefs.getBoolean("distanceTouch", true)) { + mDistanceTouch = new DistanceTouchOverlay(mMap, this); + mMap.layers().add(mDistanceTouch); + } + + mCompass = new Compass(this, mMap); + mMap.layers().add(mCompass); + + mLocation = new LocationHandler(this, mCompass); + + App.poiSearch = new POISearch(); + App.routeSearch = new RouteSearch(); + + registerForContextMenu(App.view); + + handleIntent(getIntent(), true); + } + + public Compass getCompass() { + return mCompass; + } + + public LocationHandler getLocationHandler() { + return mLocation; + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + handleIntent(intent, false); + } + + private void handleIntent(Intent intent, boolean start) { + if (intent == null) + return; + + Uri uri = intent.getData(); + if (uri != null) { + String scheme = uri.getSchemeSpecificPart(); + log.debug("got intent: " + (scheme == null ? "" : scheme)); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.options_menu, menu); + mMenu = menu; + toggleMenuCheck(); + return true; + } + + @SuppressWarnings("deprecation") + @Override + public boolean onOptionsItemSelected(MenuItem item) { + + switch (item.getItemId()) { + case R.id.menu_info_about: + startActivity(new Intent(this, InfoView.class)); + break; + + case R.id.menu_position: + break; + + case R.id.menu_poi_nearby: + Intent intent = new Intent(this, POIActivity.class); + startActivityForResult(intent, TileMap.POIS_REQUEST); + break; + + case R.id.menu_compass_2d: + if (!item.isChecked()) { + // FIXME + //mMapView.getMapViewPosition().setTilt(0); + mCompass.setMode(Compass.Mode.C2D); + } else { + mCompass.setMode(Compass.Mode.OFF); + } + break; + + case R.id.menu_compass_3d: + if (!item.isChecked()) { + mCompass.setMode(Compass.Mode.C3D); + } else { + mCompass.setMode(Compass.Mode.OFF); + } + break; + + case R.id.menu_position_my_location_enable: + if (!item.isChecked()) { + mLocation.setMode(LocationHandler.Mode.SHOW); + mLocation.setCenterOnFirstFix(); + } else { + mLocation.setMode(LocationHandler.Mode.OFF); + } + break; + + case R.id.menu_position_follow_location: + if (!item.isChecked()) { + mLocation.setMode(LocationHandler.Mode.SNAP); + } else { + mLocation.setMode(LocationHandler.Mode.OFF); + } + break; + + case R.id.menu_layer_openstreetmap: + case R.id.menu_layer_naturalearth: + int bgId = item.getItemId(); + // toggle if already enabled + if (bgId == mMapLayers.getBackgroundId()) + bgId = -1; + + mMapLayers.setBackgroundMap(bgId); + mMap.updateMap(true); + break; + + case R.id.menu_layer_grid: + mMapLayers.enableGridOverlay(!mMapLayers.isGridEnabled()); + mMap.updateMap(true); + break; + + case R.id.menu_position_enter_coordinates: + showDialog(DIALOG_ENTER_COORDINATES); + break; + + //case R.id.menu_position_map_center: + // MapPosition mapCenter = mBaseLayer.getMapFileCenter(); + // if (mapCenter != null) + // mMap.setCenter(mapCenter.getGeoPoint()); + // break; + + case R.id.menu_preferences: + startActivity(new Intent(this, EditPreferences.class)); + overridePendingTransition(R.anim.slide_right, R.anim.slide_left2); + break; + + default: + return false; + } + + toggleMenuCheck(); + + return true; + } + + private void toggleMenuCheck() { + + mMenu.findItem(R.id.menu_compass_2d) + .setChecked(mCompass.getMode() == Compass.Mode.C2D); + mMenu.findItem(R.id.menu_compass_3d) + .setChecked(mCompass.getMode() == Compass.Mode.C3D); + + mMenu.findItem(R.id.menu_position_my_location_enable) + .setChecked(mLocation.getMode() == LocationHandler.Mode.SHOW); + mMenu.findItem(R.id.menu_position_follow_location) + .setChecked(mLocation.getMode() == LocationHandler.Mode.SNAP); + + int bgId = mMapLayers.getBackgroundId(); + mMenu.findItem(R.id.menu_layer_naturalearth) + .setChecked(bgId == R.id.menu_layer_naturalearth); + + mMenu.findItem(R.id.menu_layer_openstreetmap) + .setChecked(bgId == R.id.menu_layer_openstreetmap); + + mMenu.findItem(R.id.menu_layer_grid) + .setChecked(mMapLayers.isGridEnabled()); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + + if (!isPreHoneyComb()) { + menu.clear(); + onCreateOptionsMenu(menu); + } + + menu.findItem(R.id.menu_position_map_center).setVisible(false); + + return super.onPrepareOptionsMenu(menu); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + switch (requestCode) { + case POIS_REQUEST: + log.debug("result: POIS_REQUEST"); + if (resultCode == RESULT_OK) { + int id = intent.getIntExtra("ID", 0); + log.debug("result: POIS_REQUEST: " + id); + + App.poiSearch.poiMarkers.showBubbleOnItem(id); + POI poi = App.poiSearch.getPOIs().get(id); + + if (poi.bbox != null) + mMap.animator().animateTo(poi.bbox); + else + mMap.animator().animateTo(poi.location); + } + break; + default: + break; + } + } + + static boolean isPreHoneyComb() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB; + } + + @Override + protected Dialog onCreateDialog(int id) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + if (id == DIALOG_ENTER_COORDINATES) { + if (mLocationDialog == null) + mLocationDialog = new LocationDialog(); + + return mLocationDialog.createDialog(this); + + } else if (id == DIALOG_LOCATION_PROVIDER_DISABLED) { + builder.setIcon(android.R.drawable.ic_menu_info_details); + builder.setTitle(R.string.error); + builder.setMessage(R.string.no_location_provider_available); + builder.setPositiveButton(R.string.ok, null); + return builder.create(); + } else { + // no dialog will be created + return null; + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + } + + @Override + protected void onPause() { + super.onPause(); + mCompass.pause(); + mLocation.pause(); + } + + LocationDialog mLocationDialog; + + @SuppressWarnings("deprecation") + @Override + protected void onPrepareDialog(int id, final Dialog dialog) { + if (id == DIALOG_ENTER_COORDINATES) { + + mLocationDialog.prepareDialog(mMap, dialog); + + } else { + super.onPrepareDialog(id, dialog); + } + } + + @Override + protected void onResume() { + super.onResume(); + + mCompass.resume(); + mLocation.resume(); + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + mMapLayers.setPreferences(preferences); + + if (preferences.getBoolean("fullscreen", false)) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + } + + App.lockOrientation(this); + + boolean distanceTouch = preferences.getBoolean("distanceTouch", true); + if (distanceTouch) { + if (mDistanceTouch == null) { + mDistanceTouch = new DistanceTouchOverlay(mMap, this); + mMap.layers().add(mDistanceTouch); + } + } else { + mMap.layers().remove(mDistanceTouch); + mDistanceTouch = null; + } + + mMap.updateMap(true); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + } + + /** + * Uses the UI thread to display the given text message as toast + * notification. + * + * @param text the text message to display + */ + public void showToastOnUiThread(final String text) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast toast = Toast.makeText(TileMap.this, text, Toast.LENGTH_SHORT); + toast.show(); + } + }); + } + + private enum Mode { + DEFAULT, + SHOW_LOCATION, + SNAP_LOCATION, + COMPASS_2D, + COMPASS_3D, + } + + private int mMapMode = 0; + + public void toggleLocation(View V) { + + ((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50); + + mMapMode += 1; + mMapMode %= Mode.values().length; + + setInteractionMode(mMapMode); + } + + private void setInteractionMode(int mapMode) { + Mode m = Mode.values()[mapMode]; + + switch (m) { + case DEFAULT: + + mLocation.setMode(LocationHandler.Mode.OFF); + mCompass.setMode(Compass.Mode.OFF); + + App.activity.showToastOnUiThread("Manual"); + + break; + case SHOW_LOCATION: + mLocation.setMode(LocationHandler.Mode.SHOW); + mCompass.setMode(Compass.Mode.OFF); + App.activity.showToastOnUiThread(App.activity + .getString(R.string.menu_position_my_location_enable)); + break; + + case SNAP_LOCATION: + mLocation.setMode(LocationHandler.Mode.SNAP); + mCompass.setMode(Compass.Mode.OFF); + App.activity.showToastOnUiThread(App.activity + .getString(R.string.menu_position_follow_location)); + break; + + case COMPASS_2D: + // FIXME + //mMapView.getMapViewPosition().setTilt(0); + + mLocation.setMode(LocationHandler.Mode.SHOW); + mCompass.setMode(Compass.Mode.C2D); + App.activity.showToastOnUiThread("Compass 2D"); + break; + + case COMPASS_3D: + mLocation.setMode(LocationHandler.Mode.SHOW); + mCompass.setMode(Compass.Mode.C3D); + App.activity.showToastOnUiThread("Compass 3D"); + break; + + default: + break; + } + + App.map.updateMap(true); + } + + /** + * Context Menu when clicking on the {@link Map} + */ + private GeoPoint mLongPressGeoPoint; + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.map_menu, menu); + + if (App.poiSearch.getPOIs().isEmpty()) + menu.removeItem(R.id.menu_poi_clear); + + if (App.routeSearch.isEmpty()) + menu.removeItem(R.id.menu_route_clear); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + if (App.poiSearch.onContextItemSelected(item, mLongPressGeoPoint)) + return true; + + if (App.routeSearch.onContextItemSelected(item, mLongPressGeoPoint)) + return true; + + return super.onContextItemSelected(item); + } + + /** + * MapEventsReceiver implementation + */ + @Override + public boolean singleTapUpHelper(GeoPoint p) { + return false; + } + + @Override + public boolean longPressHelper(GeoPoint p) { + mLongPressGeoPoint = p; + openContextMenu(App.view); + return true; + } + + @Override + public boolean longPressHelper(final GeoPoint p1, final GeoPoint p2) { + ((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50); + showToastOnUiThread("Distance Touch!"); + App.routeSearch.showRoute(p1, p2); + return true; + } +} diff --git a/vtm-app/src/org/oscim/app/filefilter/FilterByFileExtension.java b/vtm-app/src/org/oscim/app/filefilter/FilterByFileExtension.java new file mode 100644 index 00000000..75a3c60a --- /dev/null +++ b/vtm-app/src/org/oscim/app/filefilter/FilterByFileExtension.java @@ -0,0 +1,47 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app.filefilter; + +import java.io.File; +import java.io.FileFilter; + +/** + * Accepts all readable directories and all readable files with a given + * extension. + */ +public class FilterByFileExtension implements FileFilter { + private final String extension; + + /** + * @param extension the allowed file name extension. + */ + public FilterByFileExtension(String extension) { + this.extension = extension; + } + + @Override + public boolean accept(File file) { + // accept only readable files + if (file.canRead()) { + if (file.isDirectory()) { + // accept all directories + return true; + } else if (file.isFile() && file.getName().endsWith(this.extension)) { + return true; + } + } + return false; + } +} diff --git a/vtm-app/src/org/oscim/app/filefilter/ValidFileFilter.java b/vtm-app/src/org/oscim/app/filefilter/ValidFileFilter.java new file mode 100644 index 00000000..2c9e12d5 --- /dev/null +++ b/vtm-app/src/org/oscim/app/filefilter/ValidFileFilter.java @@ -0,0 +1,29 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app.filefilter; + +import org.oscim.tiling.TileSource.OpenResult; + +import java.io.FileFilter; + +/** + * An extension of the {@link FileFilter} interface. + */ +public interface ValidFileFilter extends FileFilter { + /** + * @return the result of the last {@link #accept} call (might be null). + */ + OpenResult getFileOpenResult(); +} diff --git a/vtm-app/src/org/oscim/app/filefilter/ValidMapFile.java b/vtm-app/src/org/oscim/app/filefilter/ValidMapFile.java new file mode 100644 index 00000000..761a9c35 --- /dev/null +++ b/vtm-app/src/org/oscim/app/filefilter/ValidMapFile.java @@ -0,0 +1,42 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app.filefilter; + +import org.oscim.tiling.TileSource.OpenResult; +import org.oscim.tiling.source.mapfile.MapFileTileSource; + +import java.io.File; + +/** + * Accepts all valid map files. + */ +public final class ValidMapFile implements ValidFileFilter { + private OpenResult openResult; + + @Override + public boolean accept(File file) { + MapFileTileSource mapFileSource = new MapFileTileSource(); + mapFileSource.setMapFile(file.getAbsolutePath()); + + this.openResult = mapFileSource.open(); + mapFileSource.close(); + return this.openResult.isSuccess(); + } + + @Override + public OpenResult getFileOpenResult() { + return this.openResult; + } +} diff --git a/vtm-app/src/org/oscim/app/filefilter/ValidRenderTheme.java b/vtm-app/src/org/oscim/app/filefilter/ValidRenderTheme.java new file mode 100644 index 00000000..052eea4a --- /dev/null +++ b/vtm-app/src/org/oscim/app/filefilter/ValidRenderTheme.java @@ -0,0 +1,72 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * Copyright 2016 devemux86 + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app.filefilter; + +import org.oscim.theme.XmlThemeBuilder; +import org.oscim.tiling.TileSource.OpenResult; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; + +/** + * Accepts all valid render theme XML files. + */ +public final class ValidRenderTheme implements ValidFileFilter { + private OpenResult openResult; + + @Override + public boolean accept(File file) { + InputStream inputStream = null; + + try { + inputStream = new FileInputStream(file); + XmlThemeBuilder renderThemeHandler = new XmlThemeBuilder(file.getParent()); + XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader(); + xmlReader.setContentHandler(renderThemeHandler); + xmlReader.parse(new InputSource(inputStream)); + this.openResult = OpenResult.SUCCESS; + } catch (ParserConfigurationException e) { + this.openResult = new OpenResult(e.getMessage()); + } catch (SAXException e) { + this.openResult = new OpenResult(e.getMessage()); + } catch (IOException e) { + this.openResult = new OpenResult(e.getMessage()); + } finally { + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + this.openResult = new OpenResult(e.getMessage()); + } + } + + return this.openResult.isSuccess(); + } + + @Override + public OpenResult getFileOpenResult() { + return this.openResult; + } +} diff --git a/vtm-app/src/org/oscim/app/filepicker/FilePicker.java b/vtm-app/src/org/oscim/app/filepicker/FilePicker.java new file mode 100755 index 00000000..764e3514 --- /dev/null +++ b/vtm-app/src/org/oscim/app/filepicker/FilePicker.java @@ -0,0 +1,262 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app.filepicker; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.GridView; + +import org.oscim.app.R; +import org.oscim.app.filefilter.ValidFileFilter; + +import java.io.File; +import java.io.FileFilter; +import java.util.Arrays; +import java.util.Comparator; + +/** + * A FilePicker displays the contents of directories. The user can navigate + * within the file system and select a single + * file whose path is then returned to the calling activity. The ordering of + * directory contents can be specified via + * {@link #setFileComparator(Comparator)}. By default subfolders and files are + * grouped and each group is ordered + * alphabetically. + * <p/> + * A {@link FileFilter} can be activated via + * {@link #setFileDisplayFilter(FileFilter)} to restrict the displayed files and + * folders. By default all files and folders are visible. + * <p/> + * Another <code>FileFilter</code> can be applied via + * {@link #setFileSelectFilter(ValidFileFilter)} to check if a selected file is + * valid before its path is returned. By default all files are considered as + * valid and can be selected. + */ +public class FilePicker extends Activity implements AdapterView.OnItemClickListener { + /** + * The name of the extra data in the result {@link Intent}. + */ + public static final String SELECTED_FILE = "selectedFile"; + + private static final String CURRENT_DIRECTORY = "currentDirectory"; + private static final String DEFAULT_DIRECTORY = "/"; + private static final int DIALOG_FILE_INVALID = 0; + // private static final int DIALOG_FILE_SELECT = 1; + private static Comparator<File> fileComparator = getDefaultFileComparator(); + private static FileFilter fileDisplayFilter; + private static ValidFileFilter fileSelectFilter; + private static final String PREFERENCES_FILE = "FilePicker"; + + /** + * Sets the file comparator which is used to order the contents of all + * directories before displaying them. If set to + * null, subfolders and files will not be ordered. + * + * @param fileComparator the file comparator (may be null). + */ + public static void setFileComparator(Comparator<File> fileComparator) { + FilePicker.fileComparator = fileComparator; + } + + /** + * Sets the file display filter. This filter is used to determine which + * files and subfolders of directories will be + * displayed. If set to null, all files and subfolders are shown. + * + * @param fileDisplayFilter the file display filter (may be null). + */ + public static void setFileDisplayFilter(FileFilter fileDisplayFilter) { + FilePicker.fileDisplayFilter = fileDisplayFilter; + } + + /** + * Sets the file select filter. This filter is used when the user selects a + * file to determine if it is valid. If set + * to null, all files are considered as valid. + * + * @param fileSelectFilter the file selection filter (may be null). + */ + public static void setFileSelectFilter(ValidFileFilter fileSelectFilter) { + FilePicker.fileSelectFilter = fileSelectFilter; + } + + /** + * Creates the default file comparator. + * + * @return the default file comparator. + */ + private static Comparator<File> getDefaultFileComparator() { + // order all files by type and alphabetically by name + return new Comparator<File>() { + @Override + public int compare(File file1, File file2) { + if (file1.isDirectory() && !file2.isDirectory()) { + return -1; + } else if (!file1.isDirectory() && file2.isDirectory()) { + return 1; + } else { + return file1.getName().compareToIgnoreCase(file2.getName()); + } + } + }; + } + + private File currentDirectory; + private FilePickerIconAdapter filePickerIconAdapter; + private File[] files; + private File[] filesWithParentFolder; + + @SuppressWarnings("deprecation") + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + File selectedFile = this.files[(int) id]; + if (selectedFile.isDirectory()) { + this.currentDirectory = selectedFile; + browseToCurrentDirectory(); + } else if (fileSelectFilter == null || fileSelectFilter.accept(selectedFile)) { + setResult(RESULT_OK, + new Intent().putExtra(SELECTED_FILE, selectedFile.getAbsolutePath())); + finish(); + } else { + showDialog(DIALOG_FILE_INVALID); + } + } + + /** + * Browses to the current directory. + */ + private void browseToCurrentDirectory() { + setTitle(this.currentDirectory.getAbsolutePath()); + + // read the subfolders and files from the current directory + if (fileDisplayFilter == null) { + this.files = this.currentDirectory.listFiles(); + } else { + this.files = this.currentDirectory.listFiles(fileDisplayFilter); + } + + if (this.files == null) { + this.files = new File[0]; + } else { + // order the subfolders and files + Arrays.sort(this.files, fileComparator); + } + + // if a parent directory exists, add it at the first position + if (this.currentDirectory.getParentFile() != null) { + this.filesWithParentFolder = new File[this.files.length + 1]; + this.filesWithParentFolder[0] = this.currentDirectory.getParentFile(); + System.arraycopy(this.files, 0, this.filesWithParentFolder, 1, + this.files.length); + this.files = this.filesWithParentFolder; + this.filePickerIconAdapter.setFiles(this.files, true); + } else { + this.filePickerIconAdapter.setFiles(this.files, false); + } + this.filePickerIconAdapter.notifyDataSetChanged(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_file_picker); + + this.filePickerIconAdapter = new FilePickerIconAdapter(this); + GridView gridView = (GridView) findViewById(R.id.filePickerView); + gridView.setOnItemClickListener(this); + gridView.setAdapter(this.filePickerIconAdapter); + + // if (savedInstanceState == null) { + // // first start of this instance + // showDialog(DIALOG_FILE_SELECT); + // } + } + + @Override + protected Dialog onCreateDialog(int id) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + switch (id) { + case DIALOG_FILE_INVALID: + builder.setIcon(android.R.drawable.ic_menu_info_details); + builder.setTitle(R.string.error); + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(getString(R.string.file_invalid)); + stringBuilder.append("\n\n"); + stringBuilder.append(FilePicker.fileSelectFilter.getFileOpenResult() + .getErrorMessage()); + + builder.setMessage(stringBuilder.toString()); + builder.setPositiveButton(R.string.ok, null); + return builder.create(); + // case DIALOG_FILE_SELECT: + // builder.setMessage(R.string.file_select); + // builder.setPositiveButton(R.string.ok, null); + // return builder.create(); + default: + // do dialog will be created + return null; + } + } + + @Override + protected void onPause() { + super.onPause(); + // save the current directory + Editor editor = getSharedPreferences(PREFERENCES_FILE, MODE_PRIVATE).edit(); + editor.clear(); + if (this.currentDirectory != null) { + editor.putString(CURRENT_DIRECTORY, this.currentDirectory.getAbsolutePath()); + } + editor.commit(); + } + + @TargetApi(11) + @Override + protected void onResume() { + super.onResume(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) + getActionBar().hide(); + + // check if the full screen mode should be activated + // if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("fullscreen", + // false)) { + // getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + // getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + // } else { + // getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + // getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + // } + + // restore the current directory + SharedPreferences preferences = getSharedPreferences(PREFERENCES_FILE, + MODE_PRIVATE); + this.currentDirectory = new File(preferences.getString(CURRENT_DIRECTORY, + DEFAULT_DIRECTORY)); + if (!this.currentDirectory.exists() || !this.currentDirectory.canRead()) { + this.currentDirectory = new File(DEFAULT_DIRECTORY); + } + browseToCurrentDirectory(); + } +} diff --git a/vtm-app/src/org/oscim/app/filepicker/FilePickerIconAdapter.java b/vtm-app/src/org/oscim/app/filepicker/FilePickerIconAdapter.java new file mode 100755 index 00000000..5ece834e --- /dev/null +++ b/vtm-app/src/org/oscim/app/filepicker/FilePickerIconAdapter.java @@ -0,0 +1,114 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app.filepicker; + +import android.content.Context; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import org.oscim.app.R; + +import java.io.File; + +/** + * An adapter for the FilePicker GridView. + */ +class FilePickerIconAdapter extends BaseAdapter { + private final Context context; + private File currentFile; + private File[] files; + private boolean hasParentFolder; + private TextView textView; + + /** + * Creates a new FilePickerIconAdapter with the given context. + * + * @param context the context of this adapter, through which new Views are + * created. + */ + FilePickerIconAdapter(Context context) { + super(); + this.context = context; + } + + @Override + public int getCount() { + if (this.files == null) { + return 0; + } + return this.files.length; + } + + @Override + public Object getItem(int index) { + return this.files[index]; + } + + @Override + public long getItemId(int index) { + return index; + } + + @Override + public View getView(int index, View convertView, ViewGroup parent) { + if (convertView instanceof TextView) { + // recycle the old view + this.textView = (TextView) convertView; + } else { + // create a new view object + this.textView = new TextView(this.context); + this.textView.setLines(2); + this.textView.setGravity(Gravity.CENTER_HORIZONTAL); + this.textView.setPadding(5, 10, 5, 10); + } + + if (index == 0 && this.hasParentFolder) { + // the parent directory of the current folder + this.textView.setCompoundDrawablesWithIntrinsicBounds(0, + R.drawable.file_picker_back, 0, 0); + this.textView.setText(".."); + } else { + this.currentFile = this.files[index]; + if (this.currentFile.isDirectory()) { + this.textView.setCompoundDrawablesWithIntrinsicBounds(0, + R.drawable.file_picker_folder, + 0, + 0); + } else { + this.textView.setCompoundDrawablesWithIntrinsicBounds(0, + R.drawable.file_picker_file, + 0, + 0); + } + this.textView.setText(this.currentFile.getName()); + } + return this.textView; + } + + /** + * Sets the data of this adapter. + * + * @param files the new files for this adapter. + * @param newHasParentFolder true if the file array has a parent folder at index 0, false + * otherwise. + */ + void setFiles(File[] files, boolean newHasParentFolder) { + this.files = files.clone(); + this.hasParentFolder = newHasParentFolder; + } +} diff --git a/vtm-app/src/org/oscim/app/location/Compass.java b/vtm-app/src/org/oscim/app/location/Compass.java new file mode 100644 index 00000000..cdfbd8df --- /dev/null +++ b/vtm-app/src/org/oscim/app/location/Compass.java @@ -0,0 +1,392 @@ +/* + * Copyright 2013 Ahmad Saleem + * Copyright 2013 Hannes Janetzek + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.oscim.app.location; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.view.animation.Animation; +import android.view.animation.RotateAnimation; +import android.widget.ImageView; + +import org.oscim.app.App; +import org.oscim.app.R; +import org.oscim.core.MapPosition; +import org.oscim.event.Event; +import org.oscim.layers.Layer; +import org.oscim.map.Map; + +@SuppressWarnings("deprecation") +public class Compass extends Layer implements SensorEventListener, + Map.UpdateListener { + + // final static Logger log = LoggerFactory.getLogger(Compass.class); + + public enum Mode { + OFF, C2D, C3D, + } + + private final SensorManager mSensorManager; + private final ImageView mArrowView; + + // private final float[] mRotationM = new float[9]; + private final float[] mRotationV = new float[3]; + + // private float[] mAccelV = new float[3]; + // private float[] mMagnetV = new float[3]; + // private boolean mLastAccelerometerSet; + // private boolean mLastMagnetometerSet; + + private float mCurRotation; + private float mCurTilt; + + private boolean mControlOrientation; + + private Mode mMode = Mode.OFF; + private int mListeners; + + @Override + public void onMapEvent(Event e, MapPosition mapPosition) { + if (!mControlOrientation) { + float rotation = -mapPosition.bearing; + adjustArrow(rotation, rotation); + } + } + + public Compass(Context context, Map map) { + super(map); + + mSensorManager = (SensorManager) context + .getSystemService(Context.SENSOR_SERVICE); + + // List<Sensor> s = mSensorManager.getSensorList(Sensor.TYPE_ALL); + // for (Sensor sensor : s) + // log.debug(sensor.toString()); + + mArrowView = (ImageView) App.activity.findViewById(R.id.compass); + + setEnabled(false); + } + + public synchronized float getRotation() { + return mCurRotation; + } + + public void controlView(boolean enable) { + mControlOrientation = enable; + } + + public boolean controlView() { + return mControlOrientation; + } + + public void setMode(Mode mode) { + if (mode == mMode) + return; + + if (mode == Mode.OFF) { + setEnabled(false); + + mMap.getEventLayer().enableRotation(true); + mMap.getEventLayer().enableTilt(true); + } else if (mMode == Mode.OFF) { + setEnabled(true); + } + + if (mode == Mode.C3D) { + mMap.getEventLayer().enableRotation(false); + mMap.getEventLayer().enableTilt(false); + } else if (mode == Mode.C2D) { + mMap.getEventLayer().enableRotation(false); + mMap.getEventLayer().enableTilt(true); + } + + mMode = mode; + } + + public Mode getMode() { + return mMode; + } + + @Override + public void setEnabled(boolean enabled) { + mListeners += enabled ? 1 : -1; + + if (mListeners == 1) { + resume(); + } else if (mListeners == 0) { + pause(); + + } else if (mListeners < 0) { + // then bad + mListeners = 0; + } + } + + public void resume() { + if (mListeners <= 0) + return; + + super.setEnabled(true); + + Sensor sensor; + // Sensor sensor = + // mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); + // Sensor sensor = + // mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + // sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); + // mSensorManager.registerListener(this, sensor, + // SensorManager.SENSOR_DELAY_UI); + // sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); + // mSensorManager.registerListener(this, sensor, + // SensorManager.SENSOR_DELAY_UI); + + sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); + mSensorManager.registerListener(this, sensor, + SensorManager.SENSOR_DELAY_UI); + + // mLastAccelerometerSet = false; + // mLastMagnetometerSet = false; + } + + public void pause() { + if (mListeners <= 0) + return; + + super.setEnabled(false); + mSensorManager.unregisterListener(this); + } + + public void adjustArrow(float prev, float cur) { + Animation an = new RotateAnimation(-prev, + -cur, + Animation.RELATIVE_TO_SELF, + 0.5f, + Animation.RELATIVE_TO_SELF, + 0.5f); + + an.setDuration(100); + an.setRepeatCount(0); + an.setFillAfter(true); + + mArrowView.startAnimation(an); + } + + @Override + public void onSensorChanged(SensorEvent event) { + + if (event.sensor.getType() != Sensor.TYPE_ORIENTATION) + return; + System.arraycopy(event.values, 0, mRotationV, 0, event.values.length); + + // SensorManager.getRotationMatrixFromVector(mRotationM, event.values); + // SensorManager.getOrientation(mRotationM, mRotationV); + + // int type = event.sensor.getType(); + // if (type == Sensor.TYPE_ACCELEROMETER) { + // System.arraycopy(event.values, 0, mAccelV, 0, event.values.length); + // mLastAccelerometerSet = true; + // } else if (type == Sensor.TYPE_MAGNETIC_FIELD) { + // System.arraycopy(event.values, 0, mMagnetV, 0, event.values.length); + // mLastMagnetometerSet = true; + // } else { + // return; + // } + // if (!mLastAccelerometerSet || !mLastMagnetometerSet) + // return; + + // SensorManager.getRotationMatrix(mRotationM, null, mAccelV, mMagnetV); + // SensorManager.getOrientation(mRotationM, mRotationV); + + // float rotation = (float) Math.toDegrees(mRotationV[0]); + float rotation = mRotationV[0]; + + // handle(event); + // if (!mOrientationOK) + // return; + // float rotation = (float) Math.toDegrees(mAzimuthRadians); + + float change = rotation - mCurRotation; + if (change > 180) + change -= 360; + else if (change < -180) + change += 360; + + // low-pass + change *= 0.05; + + rotation = mCurRotation + change; + + if (rotation > 180) + rotation -= 360; + else if (rotation < -180) + rotation += 360; + + // float tilt = (float) Math.toDegrees(mRotationV[1]); + // float tilt = (float) Math.toDegrees(mPitchAxisRadians); + float tilt = mRotationV[1]; + + mCurTilt = mCurTilt + 0.2f * (tilt - mCurTilt); + + if (mMode != Mode.OFF) { + boolean redraw = false; + + if (Math.abs(change) > 0.01) { + adjustArrow(mCurRotation, rotation); + mMap.viewport().setRotation(-rotation); + redraw = true; + } + + if (mMode == Mode.C3D) + redraw |= mMap.viewport().setTilt(-mCurTilt * 1.5f); + + if (redraw) + mMap.updateMap(true); + } + mCurRotation = rotation; + } + + // from http://stackoverflow.com/questions/16317599/android-compass-that- + // can-compensate-for-tilt-and-pitch/16386066#16386066 + + // private int mGravityAccuracy; + // private int mMagneticFieldAccuracy; + + // private float[] mGravityV = new float[3]; + // private float[] mMagFieldV = new float[3]; + // private float[] mEastV = new float[3]; + // private float[] mNorthV = new float[3]; + // + // private float mNormGravity; + // private float mNormMagField; + // + // private boolean mOrientationOK; + // private float mAzimuthRadians; + // private float mPitchRadians; + // private float mPitchAxisRadians; + // + // private void handle(SensorEvent event) { + // int SensorType = event.sensor.getType(); + // switch (SensorType) { + // case Sensor.TYPE_GRAVITY: + // mLastAccelerometerSet = true; + // System.arraycopy(event.values, 0, mGravityV, 0, mGravityV.length); + // mNormGravity = (float) Math.sqrt(mGravityV[0] * mGravityV[0] + // + mGravityV[1] * mGravityV[1] + mGravityV[2] + // * mGravityV[2]); + // for (int i = 0; i < mGravityV.length; i++) + // mGravityV[i] /= mNormGravity; + // break; + // case Sensor.TYPE_MAGNETIC_FIELD: + // mLastMagnetometerSet = true; + // System.arraycopy(event.values, 0, mMagFieldV, 0, mMagFieldV.length); + // mNormMagField = (float) Math.sqrt(mMagFieldV[0] * mMagFieldV[0] + // + mMagFieldV[1] * mMagFieldV[1] + mMagFieldV[2] + // * mMagFieldV[2]); + // for (int i = 0; i < mMagFieldV.length; i++) + // mMagFieldV[i] /= mNormMagField; + // break; + // } + // if (!mLastAccelerometerSet || !mLastMagnetometerSet) + // return; + // + // // first calculate the horizontal vector that points due east + // float ex = mMagFieldV[1] * mGravityV[2] - mMagFieldV[2] * mGravityV[1]; + // float ey = mMagFieldV[2] * mGravityV[0] - mMagFieldV[0] * mGravityV[2]; + // float ez = mMagFieldV[0] * mGravityV[1] - mMagFieldV[1] * mGravityV[0]; + // float normEast = (float) Math.sqrt(ex * ex + ey * ey + ez * ez); + // + // if (mNormGravity * mNormMagField * normEast < 0.1f) { // Typical values + // are > 100. + // // device is close to free fall (or in space?), or close to magnetic + // north pole. + // mOrientationOK = false; + // return; + // } + // + // mEastV[0] = ex / normEast; + // mEastV[1] = ey / normEast; + // mEastV[2] = ez / normEast; + // + // // next calculate the horizontal vector that points due north + // float mdotG = (mGravityV[0] * mMagFieldV[0] + // + mGravityV[1] * mMagFieldV[1] + // + mGravityV[2] * mMagFieldV[2]); + // + // float nx = mMagFieldV[0] - mGravityV[0] * mdotG; + // float ny = mMagFieldV[1] - mGravityV[1] * mdotG; + // float nz = mMagFieldV[2] - mGravityV[2] * mdotG; + // float normNorth = (float) Math.sqrt(nx * nx + ny * ny + nz * nz); + // + // mNorthV[0] = nx / normNorth; + // mNorthV[1] = ny / normNorth; + // mNorthV[2] = nz / normNorth; + // + // // take account of screen rotation away from its natural rotation + // //int rotation = + // App.activity.getWindowManager().getDefaultDisplay().getRotation(); + // float screenDirection = 0; + // //switch(rotation) { + // // case Surface.ROTATION_0: screenDirection = 0; break; + // // case Surface.ROTATION_90: screenDirection = (float)Math.PI/2; break; + // // case Surface.ROTATION_180: screenDirection = (float)Math.PI; break; + // // case Surface.ROTATION_270: screenDirection = 3*(float)Math.PI/2; + // break; + // //} + // // NB: the rotation matrix has now effectively been calculated. It + // consists of + // // the three vectors mEastV[], mNorthV[] and mGravityV[] + // + // // calculate all the required angles from the rotation matrix + // // NB: see + // http://math.stackexchange.com/questions/381649/whats-the-best-3d-angular- + // // co-ordinate-system-for-working-with-smartfone-apps + // float sin = mEastV[1] - mNorthV[0], cos = mEastV[0] + mNorthV[1]; + // mAzimuthRadians = (float) (sin != 0 && cos != 0 ? Math.atan2(sin, cos) : + // 0); + // mPitchRadians = (float) Math.acos(mGravityV[2]); + // + // sin = -mEastV[1] - mNorthV[0]; + // cos = mEastV[0] - mNorthV[1]; + // + // float aximuthPlusTwoPitchAxisRadians = + // (float) (sin != 0 && cos != 0 ? Math.atan2(sin, cos) : 0); + // + // mPitchAxisRadians = (float) (aximuthPlusTwoPitchAxisRadians - + // mAzimuthRadians) / 2; + // mAzimuthRadians += screenDirection; + // mPitchAxisRadians += screenDirection; + // + // mOrientationOK = true; + // } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // int type = sensor.getType(); + // switch (type) { + // case Sensor.TYPE_GRAVITY: + // mGravityAccuracy = accuracy; + // break; + // case Sensor.TYPE_MAGNETIC_FIELD: + // mMagneticFieldAccuracy = accuracy; + // break; + // } + } + +} diff --git a/vtm-app/src/org/oscim/app/location/LocationDialog.java b/vtm-app/src/org/oscim/app/location/LocationDialog.java new file mode 100644 index 00000000..d37fb133 --- /dev/null +++ b/vtm-app/src/org/oscim/app/location/LocationDialog.java @@ -0,0 +1,120 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * Copyright 2012 Hannes Janetzek + * Copyright 2016 devemux86 + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app.location; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; +import android.widget.SeekBar; +import android.widget.TextView; + +import org.oscim.app.App; +import org.oscim.app.R; +import org.oscim.app.TileMap; +import org.oscim.core.MapPosition; +import org.oscim.map.Map; + +public class LocationDialog { + + public void prepareDialog(Map map, final Dialog dialog) { + EditText editText = (EditText) dialog.findViewById(R.id.latitude); + + MapPosition mapCenter = map.getMapPosition(); + + editText.setText(Double.toString(mapCenter.getLatitude())); + + editText = (EditText) dialog.findViewById(R.id.longitude); + editText.setText(Double.toString(mapCenter.getLongitude())); + + SeekBar zoomlevel = (SeekBar) dialog.findViewById(R.id.zoomLevel); + zoomlevel.setMax(20); + zoomlevel.setProgress(10); + + final TextView textView = (TextView) dialog.findViewById(R.id.zoomlevelValue); + textView.setText(String.valueOf(zoomlevel.getProgress())); + zoomlevel.setOnSeekBarChangeListener(new SeekBarChangeListener(textView)); + } + + public Dialog createDialog(final TileMap map) { + AlertDialog.Builder builder = new AlertDialog.Builder(map); + builder.setIcon(android.R.drawable.ic_menu_mylocation); + builder.setTitle(R.string.menu_position_enter_coordinates); + LayoutInflater factory = LayoutInflater.from(map); + final View view = factory.inflate(R.layout.dialog_enter_coordinates, null); + builder.setView(view); + + builder.setPositiveButton(R.string.go_to_position, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // disable GPS follow mode if it is enabled + //map.mLocation.disableSnapToLocation(); + if (map.getLocationHandler().getMode() == LocationHandler.Mode.SNAP) + map.getLocationHandler() + .setMode(LocationHandler.Mode.SHOW); + + // set the map center and zoom level + EditText latitudeView = (EditText) view + .findViewById(R.id.latitude); + EditText longitudeView = (EditText) view + .findViewById(R.id.longitude); + double latitude = Double.parseDouble(latitudeView.getText() + .toString()); + double longitude = Double.parseDouble(longitudeView.getText() + .toString()); + + SeekBar zoomLevelView = (SeekBar) view + .findViewById(R.id.zoomLevel); + + int zoom = zoomLevelView.getProgress(); + + MapPosition mapPosition = new MapPosition(); + mapPosition.setPosition(latitude, longitude); + mapPosition.setZoomLevel(zoom); + App.map.setMapPosition(mapPosition); + } + }); + builder.setNegativeButton(R.string.cancel, null); + return builder.create(); + } + + class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener { + private final TextView textView; + + SeekBarChangeListener(TextView textView) { + this.textView = textView; + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + this.textView.setText(String.valueOf(progress)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // do nothing + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // do nothing + } + } +} diff --git a/vtm-app/src/org/oscim/app/location/LocationHandler.java b/vtm-app/src/org/oscim/app/location/LocationHandler.java new file mode 100644 index 00000000..723e4f77 --- /dev/null +++ b/vtm-app/src/org/oscim/app/location/LocationHandler.java @@ -0,0 +1,236 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * Copyright 2013 Hannes Janetzek + * Copyright 2013 Ahmad Al-saleem + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app.location; + +import android.content.Context; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; + +import org.oscim.app.App; +import org.oscim.app.R; +import org.oscim.app.TileMap; +import org.oscim.core.MapPosition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LocationHandler implements LocationListener { + final static Logger log = LoggerFactory.getLogger(LocationHandler.class); + + public enum Mode { + OFF, + SHOW, + SNAP, + } + + private final static int DIALOG_LOCATION_PROVIDER_DISABLED = 2; + private final static int SHOW_LOCATION_ZOOM = 14; + + private final LocationManager mLocationManager; + private final LocationOverlay mLocationOverlay; + + private Mode mMode = Mode.OFF; + + private boolean mSetCenter; + private MapPosition mMapPosition; + + public LocationHandler(TileMap tileMap, Compass compass) { + mLocationManager = (LocationManager) tileMap + .getSystemService(Context.LOCATION_SERVICE); + + mLocationOverlay = new LocationOverlay(App.map, compass); + + mMapPosition = new MapPosition(); + } + + public boolean setMode(Mode mode) { + if (mode == mMode) + return true; + + if (mode == Mode.OFF) { + disableShowMyLocation(); + + if (mMode == Mode.SNAP) + App.map.getEventLayer().enableMove(true); + } + + if (mMode == Mode.OFF) { + if (!enableShowMyLocation()) + return false; + } + + if (mode == Mode.SNAP) { + App.map.getEventLayer().enableMove(false); + gotoLastKnownPosition(); + } else { + App.map.getEventLayer().enableMove(true); + } + + // FIXME? + mSetCenter = false; + mMode = mode; + + return true; + } + + public Mode getMode() { + return mMode; + } + + public boolean isFirstCenter() { + return mSetCenter; + } + + @SuppressWarnings("deprecation") + private boolean enableShowMyLocation() { + + Criteria criteria = new Criteria(); + criteria.setAccuracy(Criteria.ACCURACY_FINE); + String bestProvider = mLocationManager.getBestProvider(criteria, true); + + if (bestProvider == null) { + App.activity.showDialog(DIALOG_LOCATION_PROVIDER_DISABLED); + return false; + } + + mLocationManager.requestLocationUpdates(bestProvider, 10000, 10, this); + + Location location = gotoLastKnownPosition(); + if (location == null) + return false; + + mLocationOverlay.setEnabled(true); + mLocationOverlay.setPosition(location.getLatitude(), + location.getLongitude(), + location.getAccuracy()); + + // FIXME -> implement LayerGroup + App.map.layers().add(4, mLocationOverlay); + + App.map.updateMap(true); + return true; + } + + /** + * Disable "show my location" mode. + */ + private boolean disableShowMyLocation() { + + mLocationManager.removeUpdates(this); + mLocationOverlay.setEnabled(false); + + App.map.layers().remove(mLocationOverlay); + App.map.updateMap(true); + + return true; + } + + public Location gotoLastKnownPosition() { + Location location = null; + float bestAccuracy = Float.MAX_VALUE; + + for (String provider : mLocationManager.getProviders(true)) { + Location l = mLocationManager.getLastKnownLocation(provider); + if (l == null) + continue; + + float accuracy = l.getAccuracy(); + if (accuracy <= 0) + accuracy = Float.MAX_VALUE; + + if (location == null || accuracy <= bestAccuracy) { + location = l; + bestAccuracy = accuracy; + } + } + + if (location == null) { + App.activity.showToastOnUiThread(App.activity + .getString(R.string.error_last_location_unknown)); + return null; + } + + App.map.getMapPosition(mMapPosition); + + if (mMapPosition.zoomLevel < SHOW_LOCATION_ZOOM) + mMapPosition.setZoomLevel(SHOW_LOCATION_ZOOM); + + mMapPosition.setPosition(location.getLatitude(), location.getLongitude()); + App.map.setMapPosition(mMapPosition); + + return location; + } + + /*** + * LocationListener + ***/ + @Override + public void onLocationChanged(Location location) { + + if (mMode == Mode.OFF) + return; + + double lat = location.getLatitude(); + double lon = location.getLongitude(); + + log.debug("update location " + lat + ":" + lon); + + if (mSetCenter || mMode == Mode.SNAP) { + mSetCenter = false; + + App.map.getMapPosition(mMapPosition); + mMapPosition.setPosition(lat, lon); + App.map.setMapPosition(mMapPosition); + } + + mLocationOverlay.setPosition(lat, lon, location.getAccuracy()); + } + + @Override + public void onProviderDisabled(String provider) { + } + + @Override + public void onProviderEnabled(String provider) { + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + } + + public void setCenterOnFirstFix() { + mSetCenter = true; + } + + public void pause() { + if (mMode != Mode.OFF) { + log.debug("pause location listener"); + } + } + + public void resume() { + if (mMode != Mode.OFF) { + Criteria criteria = new Criteria(); + criteria.setAccuracy(Criteria.ACCURACY_FINE); + String bestProvider = mLocationManager.getBestProvider(criteria, true); + mLocationManager.requestLocationUpdates(bestProvider, 10000, 10, this); + } + } + +} diff --git a/vtm-app/src/org/oscim/app/location/LocationOverlay.java b/vtm-app/src/org/oscim/app/location/LocationOverlay.java new file mode 100644 index 00000000..c3a4dd2d --- /dev/null +++ b/vtm-app/src/org/oscim/app/location/LocationOverlay.java @@ -0,0 +1,331 @@ +/* + * Copyright 2013 Ahmad Saleem + * Copyright 2013 Hannes Janetzek + * Copyright 2016 devemux86 + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app.location; + +import android.os.SystemClock; + +import org.oscim.backend.GL; +import org.oscim.core.Box; +import org.oscim.core.MercatorProjection; +import org.oscim.core.Point; +import org.oscim.core.Tile; +import org.oscim.layers.Layer; +import org.oscim.map.Map; +import org.oscim.renderer.GLShader; +import org.oscim.renderer.GLState; +import org.oscim.renderer.GLViewport; +import org.oscim.renderer.LayerRenderer; +import org.oscim.renderer.MapRenderer; +import org.oscim.utils.FastMath; +import org.oscim.utils.math.Interpolation; + +import static org.oscim.backend.GLAdapter.gl; + +public class LocationOverlay extends Layer { + private final int SHOW_ACCURACY_ZOOM = 16; + + private final Point mLocation = new Point(); + private double mRadius; + + private final Compass mCompass; + + public LocationOverlay(Map map, Compass compass) { + super(map); + mRenderer = new LocationIndicator(map); + mCompass = compass; + } + + public void setPosition(double latitude, double longitude, double accuracy) { + mLocation.x = MercatorProjection.longitudeToX(longitude); + mLocation.y = MercatorProjection.latitudeToY(latitude); + mRadius = accuracy / MercatorProjection.groundResolution(latitude, 1); + ((LocationIndicator) mRenderer).animate(true); + } + + @Override + public void setEnabled(boolean enabled) { + if (enabled == isEnabled()) + return; + + super.setEnabled(enabled); + + if (!enabled) + ((LocationIndicator) mRenderer).animate(false); + + mCompass.setEnabled(enabled); + } + + public class LocationIndicator extends LayerRenderer { + private int mShaderProgram; + private int hVertexPosition; + private int hMatrixPosition; + private int hScale; + private int hPhase; + private int hDirection; + + private final float CIRCLE_SIZE = 60; + + private final static long ANIM_RATE = 50; + private final static long INTERVAL = 2000; + + private final Point mIndicatorPosition = new Point(); + + private final Point mScreenPoint = new Point(); + private final Box mBBox = new Box(); + + private boolean mInitialized; + + private boolean mLocationIsVisible; + + private boolean mRunAnim; + private long mAnimStart; + + public LocationIndicator(final Map map) { + super(); + } + + private void animate(boolean enable) { + if (mRunAnim == enable) + return; + + mRunAnim = enable; + if (!enable) + return; + + final Runnable action = new Runnable() { + private long lastRun; + + @Override + public void run() { + if (!mRunAnim) + return; + + long diff = SystemClock.elapsedRealtime() - lastRun; + mMap.postDelayed(this, Math.min(ANIM_RATE, diff)); + mMap.render(); + } + }; + + mAnimStart = SystemClock.elapsedRealtime(); + mMap.postDelayed(action, ANIM_RATE); + } + + private float animPhase() { + return (float) ((MapRenderer.frametime - mAnimStart) % INTERVAL) / INTERVAL; + } + + @Override + public void update(GLViewport v) { + + if (!mInitialized) { + init(); + mInitialized = true; + } + + if (!isEnabled()) { + setReady(false); + return; + } + + if (!v.changed() && isReady()) + return; + + setReady(true); + + int width = mMap.getWidth(); + int height = mMap.getHeight(); + + // clamp location to a position that can be + // savely translated to screen coordinates + v.getBBox(mBBox, 0); + + double x = mLocation.x; + double y = mLocation.y; + + if (!mBBox.contains(mLocation)) { + x = FastMath.clamp(x, mBBox.xmin, mBBox.xmax); + y = FastMath.clamp(y, mBBox.ymin, mBBox.ymax); + } + + // get position of Location in pixel relative to + // screen center + v.toScreenPoint(x, y, mScreenPoint); + + x = mScreenPoint.x + width / 2; + y = mScreenPoint.y + height / 2; + + // clip position to screen boundaries + int visible = 0; + + if (x > width - 5) + x = width; + else if (x < 5) + x = 0; + else + visible++; + + if (y > height - 5) + y = height; + else if (y < 5) + y = 0; + else + visible++; + + mLocationIsVisible = (visible == 2); + + // set location indicator position + v.fromScreenPoint(x, y, mIndicatorPosition); + } + + @Override + public void render(GLViewport v) { + + GLState.useProgram(mShaderProgram); + GLState.blend(true); + GLState.test(false, false); + + GLState.enableVertexArrays(hVertexPosition, -1); + MapRenderer.bindQuadVertexVBO(hVertexPosition/*, true*/); + + float radius = CIRCLE_SIZE; + + animate(true); + boolean viewShed = false; + if (!mLocationIsVisible /* || pos.zoomLevel < SHOW_ACCURACY_ZOOM */) { + //animate(true); + } else { + if (v.pos.zoomLevel >= SHOW_ACCURACY_ZOOM) + radius = (float) (mRadius * v.pos.scale); + + viewShed = true; + //animate(false); + } + gl.uniform1f(hScale, radius); + + double x = mIndicatorPosition.x - v.pos.x; + double y = mIndicatorPosition.y - v.pos.y; + double tileScale = Tile.SIZE * v.pos.scale; + + v.mvp.setTransScale((float) (x * tileScale), (float) (y * tileScale), 1); + v.mvp.multiplyMM(v.viewproj, v.mvp); + v.mvp.setAsUniform(hMatrixPosition); + + if (!viewShed) { + float phase = Math.abs(animPhase() - 0.5f) * 2; + //phase = Interpolation.fade.apply(phase); + phase = Interpolation.swing.apply(phase); + + gl.uniform1f(hPhase, 0.8f + phase * 0.2f); + } else { + gl.uniform1f(hPhase, 1); + } + + if (viewShed && mLocationIsVisible) { + float rotation = mCompass.getRotation() - 90; + gl.uniform2f(hDirection, + (float) Math.cos(Math.toRadians(rotation)), + (float) Math.sin(Math.toRadians(rotation))); + } else { + gl.uniform2f(hDirection, 0, 0); + } + + gl.drawArrays(GL.TRIANGLE_STRIP, 0, 4); + } + + private boolean init() { + int shader = GLShader.createProgram(vShaderStr, fShaderStr); + if (shader == 0) + return false; + + mShaderProgram = shader; + hVertexPosition = gl.getAttribLocation(shader, "a_pos"); + hMatrixPosition = gl.getUniformLocation(shader, "u_mvp"); + hPhase = gl.getUniformLocation(shader, "u_phase"); + hScale = gl.getUniformLocation(shader, "u_scale"); + hDirection = gl.getUniformLocation(shader, "u_dir"); + + return true; + } + + private final static String vShaderStr = "" + + "precision mediump float;" + + "uniform mat4 u_mvp;" + + "uniform float u_phase;" + + "uniform float u_scale;" + + "attribute vec2 a_pos;" + + "varying vec2 v_tex;" + + "void main() {" + + " gl_Position = u_mvp * vec4(a_pos * u_scale * u_phase, 0.0, 1.0);" + + " v_tex = a_pos;" + + "}"; + + private final static String fShaderStr = "" + + "precision mediump float;" + + "varying vec2 v_tex;" + + "uniform float u_scale;" + + "uniform float u_phase;" + + "uniform vec2 u_dir;" + + + "void main() {" + + " float len = 1.0 - length(v_tex);" + + " if (u_dir.x == 0.0 && u_dir.y == 0.0){" + + " gl_FragColor = vec4(0.2, 0.2, 0.8, 1.0) * len;" + + " } else {" + /// outer ring + + " float a = smoothstep(0.0, 2.0 / u_scale, len);" + /// inner ring + + " float b = 0.5 * smoothstep(4.0 / u_scale, 5.0 / u_scale, len);" + /// center point + + " float c = 0.5 * (1.0 - smoothstep(14.0 / u_scale, 16.0 / u_scale, 1.0 - len));" + + " vec2 dir = normalize(v_tex);" + + " float d = 1.0 - dot(dir, u_dir); " + /// 0.5 width of viewshed + + " d = clamp(step(0.5, d), 0.4, 0.7);" + /// - subtract inner from outer to create the outline + /// - multiply by viewshed + /// - add center point + + " a = d * (a - (b + c)) + c;" + + " gl_FragColor = vec4(0.2, 0.2, 0.8, 1.0) * a;" + + "}}"; + + //private final static String fShaderStr = "" + // + "precision mediump float;" + // + "varying vec2 v_tex;" + // + "uniform float u_scale;" + // + "uniform float u_phase;" + // + "uniform vec2 u_dir;" + // + "void main() {" + // + " float len = 1.0 - length(v_tex);" + // /// outer ring + // + " float a = smoothstep(0.0, 2.0 / u_scale, len);" + // /// inner ring + // + " float b = 0.8 * smoothstep(3.0 / u_scale, 4.0 / u_scale, len);" + // /// center point + // + " float c = 0.5 * (1.0 - smoothstep(14.0 / u_scale, 16.0 / u_scale, 1.0 - len));" + // + " vec2 dir = normalize(v_tex);" + // + " float d = dot(dir, u_dir); " + // /// 0.5 width of viewshed + // + " d = clamp(smoothstep(0.7, 0.7 + 2.0/u_scale, d) * len, 0.0, 1.0);" + // /// - subtract inner from outer to create the outline + // /// - multiply by viewshed + // /// - add center point + // + " a = max(d, (a - (b + c)) + c);" + // + " gl_FragColor = vec4(0.2, 0.2, 0.8, 1.0) * a;" + // + "}"; + + } +} diff --git a/vtm-app/src/org/oscim/app/preferences/CacheSizePreference.java b/vtm-app/src/org/oscim/app/preferences/CacheSizePreference.java new file mode 100644 index 00000000..02d82531 --- /dev/null +++ b/vtm-app/src/org/oscim/app/preferences/CacheSizePreference.java @@ -0,0 +1,219 @@ +package org.oscim.app.preferences; + +import android.content.Context; +import android.content.res.TypedArray; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.RelativeLayout; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; + +import org.oscim.app.R; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CacheSizePreference extends Preference implements OnSeekBarChangeListener { + final static Logger log = LoggerFactory.getLogger(CacheSizePreference.class); + + private static final String NS_OCIM_APP = "http://app.oscim.org"; + private static final int DEFAULT_VALUE = 50; + + private int mMaxValue = 50; + private int mMinValue = 0; + private int mInterval = 1; + private int mCurrentValue; + private String mUnitsLeft = ""; + private String mUnitsRight = ""; + private SeekBar mSeekBar; + + private TextView mStatusText; + + public CacheSizePreference(Context context, AttributeSet attrs) { + super(context, attrs); + initPreference(context, attrs); + } + + public CacheSizePreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initPreference(context, attrs); + } + + private void initPreference(Context context, AttributeSet attrs) { + setValuesFromXml(attrs); + mSeekBar = new SeekBar(context, attrs); + mSeekBar.setMax(mMaxValue - mMinValue); + mSeekBar.setOnSeekBarChangeListener(this); + } + + private void setValuesFromXml(AttributeSet attrs) { + //StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath()); + //double sdAvailSize = (double) stat.getAvailableBlocks() + // * (double) stat.getBlockSize(); + //One binary megabyte equals 1,048,576 bytes. + // otherwise we need an logarithmic scale to set a sane value with the slider... + int megaAvailable = 50; //(int)sdAvailSize / 1048576; + + mMaxValue = megaAvailable;//attrs.getAttributeIntValue(ANDROIDNS, "max", 100); + mMinValue = attrs.getAttributeIntValue(NS_OCIM_APP, "min", 0); + + mUnitsLeft = getAttributeStringValue(attrs, NS_OCIM_APP, "unitsLeft", ""); + //String units = getAttributeStringValue(attrs, NS_OCIM_APP, "units", ""); + mUnitsRight = "/" + String.valueOf(megaAvailable) + "MB"; + try { + String newInterval = attrs.getAttributeValue(NS_OCIM_APP, "interval"); + if (newInterval != null) + mInterval = Integer.parseInt(newInterval); + } catch (Exception e) { + log.error("", e); + } + + } + + private String getAttributeStringValue(AttributeSet attrs, String namespace, String name, + String defaultValue) { + String value = attrs.getAttributeValue(namespace, name); + if (value == null) + value = defaultValue; + + return value; + } + + @Override + protected View onCreateView(ViewGroup parent) { + + RelativeLayout layout = null; + + try { + LayoutInflater mInflater = (LayoutInflater) getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + layout = (RelativeLayout) mInflater + .inflate(R.layout.seek_bar_preference, parent, false); + } catch (Exception e) { + log.error("", e); + } + + return layout; + + } + + @SuppressWarnings("deprecation") + @Override + public void onBindView(View view) { + super.onBindView(view); + + try { + // move our seekbar to the new view we've been given + ViewParent oldContainer = mSeekBar.getParent(); + ViewGroup newContainer = (ViewGroup) view.findViewById(R.id.seekBarPrefBarContainer); + + if (oldContainer != newContainer) { + // remove the seekbar from the old view + if (oldContainer != null) { + ((ViewGroup) oldContainer).removeView(mSeekBar); + } + // remove the existing seekbar (there may not be one) and add ours + newContainer.removeAllViews(); + newContainer.addView(mSeekBar, ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + } + } catch (Exception ex) { + log.error("Error binding view: " + ex.toString()); + } + + updateView(view); + } + + /** + * Update a SeekBarPreference view with our current state + * + * @param view + */ + protected void updateView(View view) { + + try { + RelativeLayout layout = (RelativeLayout) view; + + mStatusText = (TextView) layout.findViewById(R.id.seekBarPrefValue); + mStatusText.setText(String.valueOf(mCurrentValue)); + mStatusText.setMinimumWidth(30); + + mSeekBar.setProgress(mCurrentValue - mMinValue); + + TextView unitsRight = (TextView) layout.findViewById(R.id.seekBarPrefUnitsRight); + unitsRight.setText(mUnitsRight); + + TextView unitsLeft = (TextView) layout.findViewById(R.id.seekBarPrefUnitsLeft); + unitsLeft.setText(mUnitsLeft); + + } catch (Exception e) { + log.error("", e); + } + + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + int newValue = progress + mMinValue; + + if (newValue > mMaxValue) + newValue = mMaxValue; + else if (newValue < mMinValue) + newValue = mMinValue; + else if (mInterval != 1 && newValue % mInterval != 0) + newValue = Math.round(((float) newValue) / mInterval) * mInterval; + + // change rejected, revert to the previous value + if (!callChangeListener(newValue)) { + seekBar.setProgress(mCurrentValue - mMinValue); + return; + } + + // change accepted, store it + mCurrentValue = newValue; + mStatusText.setText(String.valueOf(newValue)); + persistInt(newValue); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + notifyChanged(); + } + + @Override + protected Object onGetDefaultValue(TypedArray ta, int index) { + + int defaultValue = ta.getInt(index, DEFAULT_VALUE); + return defaultValue; + + } + + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { + + if (restoreValue) { + mCurrentValue = getPersistedInt(mCurrentValue); + } else { + int temp = 0; + try { + temp = (Integer) defaultValue; + } catch (Exception ex) { + log.error("Invalid default value: " + defaultValue.toString()); + } + + persistInt(temp); + mCurrentValue = temp; + } + + } + +} diff --git a/vtm-app/src/org/oscim/app/preferences/EditPreferences.java b/vtm-app/src/org/oscim/app/preferences/EditPreferences.java new file mode 100644 index 00000000..cf5112d3 --- /dev/null +++ b/vtm-app/src/org/oscim/app/preferences/EditPreferences.java @@ -0,0 +1,68 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app.preferences; + +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceActivity; + +import org.oscim.app.App; +import org.oscim.app.R; + +/** + * Activity to edit the application preferences. + */ +public class EditPreferences extends PreferenceActivity { + @SuppressWarnings("deprecation") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences); + + Preference button = (Preference) findPreference("clear_cache"); + button.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference arg0) { + App.activity.getMapLayers().deleteCache(); + return true; + } + }); + } + + @Override + public void finish() { + super.finish(); + overridePendingTransition(R.anim.slide_left, R.anim.slide_right2); + } + + // @TargetApi(11) + @Override + protected void onResume() { + super.onResume(); + + // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) + // getActionBar().hide(); + + // check if the full screen mode should be activated + // if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("fullscreen", + // false)) { + // getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + // getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + // } else { + // getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + // getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + // } + } +} diff --git a/vtm-app/src/org/oscim/app/preferences/SeekBarPreference.java b/vtm-app/src/org/oscim/app/preferences/SeekBarPreference.java new file mode 100644 index 00000000..5a244d29 --- /dev/null +++ b/vtm-app/src/org/oscim/app/preferences/SeekBarPreference.java @@ -0,0 +1,153 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.oscim.app.preferences; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.preference.DialogPreference; +import android.preference.PreferenceManager; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; + +/** + * This abstract class provides all code for a seek bar preference. Deriving + * classes only need to set the current and + * maximum value of the seek bar. An optional text message above the seek bar is + * also supported as well as an optional + * current value message below the seek bar. + */ +abstract class SeekBarPreference extends DialogPreference implements OnSeekBarChangeListener { + private TextView mCurrentValueTextView; + private Editor mEditor; + private SeekBar mPreferenceSeekBar; + + /** + * How much the value should increase when the seek bar is moved. + */ + int increment = 1; + + /** + * The maximum value of the seek bar. + */ + int max; + + /** + * Optional text message to display on top of the seek bar. + */ + String messageText; + + /** + * The SharedPreferences instance that is used. + */ + final SharedPreferences preferencesDefault; + + /** + * The current value of the seek bar. + */ + int seekBarCurrentValue; + + /** + * Create a new seek bar preference. + * + * @param context the context of the seek bar preferences activity. + * @param attrs A set of attributes (currently ignored). + */ + SeekBarPreference(Context context, AttributeSet attrs) { + super(context, attrs); + preferencesDefault = PreferenceManager.getDefaultSharedPreferences(context); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + // check if the "OK" button was pressed and the seek bar value has changed + if (which == DialogInterface.BUTTON_POSITIVE + && seekBarCurrentValue != mPreferenceSeekBar.getProgress()) { + // get the value of the seek bar and save it in the preferences + seekBarCurrentValue = mPreferenceSeekBar.getProgress(); + mEditor = preferencesDefault.edit(); + mEditor.putInt(getKey(), seekBarCurrentValue); + mEditor.commit(); + } + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (mCurrentValueTextView != null) { + mCurrentValueTextView.setText(getCurrentValueText(progress)); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // do nothing + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // do nothing + } + + @Override + protected View onCreateDialogView() { + // create a layout for the optional text messageText and the seek bar + LinearLayout linearLayout = new LinearLayout(getContext()); + linearLayout.setOrientation(LinearLayout.VERTICAL); + linearLayout.setPadding(20, 10, 20, 10); + + // check if a text message should appear above the seek bar + if (messageText != null) { + // create a text view for the text messageText + TextView messageTextView = new TextView(getContext()); + messageTextView.setText(messageText); + messageTextView.setPadding(0, 0, 0, 20); + // add the text message view to the layout + linearLayout.addView(messageTextView); + } + + // create the seek bar and set the maximum and current value + mPreferenceSeekBar = new SeekBar(getContext()); + mPreferenceSeekBar.setOnSeekBarChangeListener(this); + mPreferenceSeekBar.setMax(max); + mPreferenceSeekBar.setProgress(Math.min(seekBarCurrentValue, max)); + mPreferenceSeekBar.setKeyProgressIncrement(increment); + mPreferenceSeekBar.setPadding(0, 0, 0, 10); + // add the seek bar to the layout + linearLayout.addView(mPreferenceSeekBar); + + // create the text view for the current value below the seek bar + mCurrentValueTextView = new TextView(getContext()); + mCurrentValueTextView.setText(getCurrentValueText(mPreferenceSeekBar.getProgress())); + mCurrentValueTextView.setGravity(Gravity.CENTER_HORIZONTAL); + // add the current value text view to the layout + linearLayout.addView(mCurrentValueTextView); + + return linearLayout; + } + + /** + * Get the current value text. + * + * @param progress the current progress level of the seek bar. + * @return the new current value text + */ + abstract String getCurrentValueText(int progress); +} diff --git a/vtm-app/src/org/oscim/overlay/DistanceTouchOverlay.java b/vtm-app/src/org/oscim/overlay/DistanceTouchOverlay.java new file mode 100644 index 00000000..f9d57e62 --- /dev/null +++ b/vtm-app/src/org/oscim/overlay/DistanceTouchOverlay.java @@ -0,0 +1,182 @@ +/* + * Copyright 2013 Ahmad Saleem + * Copyright 2013 Hannes Janetzek + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.oscim.overlay; + +import org.oscim.core.GeoPoint; +import org.oscim.event.Event; +import org.oscim.event.Gesture; +import org.oscim.event.GestureListener; +import org.oscim.event.MotionEvent; +import org.oscim.layers.Layer; +import org.oscim.map.Map; +import org.osmdroid.overlays.MapEventsReceiver; + +import java.util.Timer; +import java.util.TimerTask; + +public class DistanceTouchOverlay extends Layer implements Map.InputListener, + GestureListener { + + private static final int LONGPRESS_THRESHOLD = 800; + + private Timer mLongpressTimer; + + private float mPrevX1, mPrevX2, mPrevY1, mPrevY2; + private float mCurX1, mCurX2, mCurY1, mCurY2; + + // private final static int POINTER_UP = -1; + // private int mPointer1 = POINTER_UP; + // private int mPointer2 = POINTER_UP; + + private final MapEventsReceiver mReceiver; + + /** + * @param map the Map + * @param receiver the object that will receive/handle the events. It must + * implement MapEventsReceiver interface. + */ + public DistanceTouchOverlay(Map map, MapEventsReceiver receiver) { + super(map); + mReceiver = receiver; + } + + @Override + public void onDetach() { + super.onDetach(); + } + + private void cancel() { + + if (mLongpressTimer != null) { + mLongpressTimer.cancel(); + mLongpressTimer = null; + } + } + + @Override + public void onInputEvent(Event event, MotionEvent e) { + + int action = e.getAction() & MotionEvent.ACTION_MASK; + + if ((action == MotionEvent.ACTION_CANCEL)) { + cancel(); + return; + } + + if (mLongpressTimer != null) { + // any pointer up while long press detection + // cancels timer + if (action == MotionEvent.ACTION_POINTER_UP + || action == MotionEvent.ACTION_UP) { + + cancel(); + return; + } + + // two fingers must still be down, tested + // one above. + if (action == MotionEvent.ACTION_MOVE) { + // update pointer positions + // int idx1 = e.findPointerIndex(mPointer1); + // int idx2 = e.findPointerIndex(mPointer2); + + mCurX1 = e.getX(0); + mCurY1 = e.getY(0); + mCurX2 = e.getX(1); + mCurY2 = e.getY(1); + + // cancel if moved one finger more than 50 pixel + float maxSq = 10 * 10; + float d = (mCurX1 - mPrevX1) * (mCurX1 - mPrevX1) + + (mCurY1 - mPrevY1) * (mCurY1 - mPrevY1); + if (d > maxSq) { + cancel(); + return; + } + d = (mCurX2 - mPrevX2) * (mCurX2 - mPrevX2) + + (mCurY2 - mPrevY2) * (mCurY2 - mPrevY2); + if (d > maxSq) { + cancel(); + return; + } + } + } + + if ((action == MotionEvent.ACTION_POINTER_DOWN) + && (e.getPointerCount() == 2)) { + // App.log.debug("down"); + + // keep track of pointer ids, only + // use these for gesture, ignoring + // more than two pointer + + // mPointer1 = e.getPointerId(0); + // mPointer2 = e.getPointerId(1); + + if (mLongpressTimer == null) { + // start timer, keep initial down position + mCurX1 = mPrevX1 = e.getX(0); + mCurY1 = mPrevY1 = e.getY(0); + mCurX2 = mPrevX2 = e.getX(1); + mCurY2 = mPrevY2 = e.getY(1); + runLongpressTimer(); + } + } + } + + // @Override + // public boolean onLongPress(MotionEvent e) { + // // dont forward long press when two fingers are down. + // // maybe should be only done if our timer is still running. + // // ... not sure if this is even needed + // GeoPoint p = mMap.getViewport().fromScreenPoint(e.getX(), e.getY()); + // return mReceiver.longPressHelper(p); + // } + + public void runLongpressTimer() { + // mMap.postDelayed(action, delay); + + mLongpressTimer = new Timer(); + mLongpressTimer.schedule(new TimerTask() { + + @Override + public void run() { + final GeoPoint p1 = mMap.viewport().fromScreenPoint(mCurX1, + mCurY1); + final GeoPoint p2 = mMap.viewport().fromScreenPoint(mCurX2, + mCurY2); + + mMap.post(new Runnable() { + @Override + public void run() { + mReceiver.longPressHelper(p1, p2); + } + }); + } + }, LONGPRESS_THRESHOLD); + } + + @Override + public boolean onGesture(Gesture g, MotionEvent e) { + if (g instanceof Gesture.LongPress) { + GeoPoint p = mMap.viewport().fromScreenPoint(e.getX(), e.getY()); + return mReceiver.longPressHelper(p); + } + return false; + } + +} diff --git a/vtm-app/src/org/osmdroid/location/FlickrPOIProvider.java b/vtm-app/src/org/osmdroid/location/FlickrPOIProvider.java new file mode 100644 index 00000000..e33f3d32 --- /dev/null +++ b/vtm-app/src/org/osmdroid/location/FlickrPOIProvider.java @@ -0,0 +1,163 @@ +package org.osmdroid.location; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.oscim.core.BoundingBox; +import org.oscim.core.GeoPoint; +import org.osmdroid.utils.BonusPackHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * POI Provider using Flickr service to get geolocalized photos. + * + * @author M.Kergall + * @see "http://www.flickr.com/services/api/flickr.photos.search.html" + */ +public class FlickrPOIProvider implements POIProvider { + + final static Logger log = LoggerFactory.getLogger(FlickrPOIProvider.class); + + protected String mApiKey; + private final static String PHOTO_URL = "http://www.flickr.com/photos/%s/%s/sizes/o/in/photostream/"; + + /** + * @param apiKey the registered API key to give to Flickr service. + * @see "http://www.flickr.com/help/api/" + */ + public FlickrPOIProvider(String apiKey) { + mApiKey = apiKey; + } + + private String getUrlInside(BoundingBox boundingBox, int maxResults) { + StringBuffer url = new StringBuffer( + "http://api.flickr.com/services/rest/?method=flickr.photos.search"); + url.append("&api_key=" + mApiKey); + url.append("&bbox=" + boundingBox.getMinLongitude()); + url.append("," + boundingBox.getMinLatitude()); + url.append("," + boundingBox.getMaxLongitude()); + url.append("," + boundingBox.getMaxLatitude()); + url.append("&has_geo=1"); + // url.append("&geo_context=2"); + // url.append("&is_commons=true"); + url.append("&format=json&nojsoncallback=1"); + url.append("&per_page=" + maxResults); + // From Flickr doc: + // "Geo queries require some sort of limiting agent in order to prevent the database from crying." + // And min_date_upload is considered as a limiting agent. So: + url.append("&min_upload_date=2005/01/01"); + + // Ask to provide some additional attributes we will need: + url.append("&extras=geo,url_sq"); + url.append("&sort=interestingness-desc"); + return url.toString(); + } + + /* public POI getPhoto(String photoId){ String url = + * "http://api.flickr.com/services/rest/?method=flickr.photos.getInfo" + * + + * "&api_key=" + mApiKey + "&photo_id=" + photo Id + + * "&format=json&nojsoncallback=1"; log.debug( * "getPhoto:"+url); String + * jString = + * BonusPackHelper.requestStringFromUrl(url); if (jString == null) + * { + * log.error( * "FlickrPOIProvider: request failed."); + * return null; } try { POI poi = new POI(POI.POI_SERVICE_FLICKR); + * JSONObject jRoot = new JSONObject(jString); JSONObject jPhoto = + * jRoot.getJSONObject("photo"); JSONObject jLocation = + * jPhoto.getJSONObject("location"); poi.mLocation = new GeoPoint( + * jLocation.getDouble("latitude"), + * jLocation.getDouble("longitude")); + * poi.mId = Long.parseLong(photoId); JSONObject jTitle = + * jPhoto.getJSONObject("title"); poi.mType = + * jTitle.getString("_content"); + * JSONObject jDescription = jPhoto.getJSONObject("description"); + * poi.mDescription = jDescription.getString("_content"); + * //truncate + * description if too long: if (poi.mDescription.length() > 300){ + * poi.mDescription = poi.mDescription.substring(0, 300) + + * " (...)"; } + * String farm = jPhoto.getString("farm"); String server = + * jPhoto.getString("server"); String secret = + * jPhoto.getString("secret"); + * JSONObject jOwner = jPhoto.getJSONObject("owner"); String nsid + * = + * jOwner.getString("nsid"); poi.mThumbnailPath = + * "http://farm"+farm+".staticflickr.com/" + * +server+"/"+photoId+"_"+secret+"_s.jpg"; poi.mUrl = + * "http://www.flickr.com/photos/"+nsid+"/"+photoId; return poi; + * }catch + * (JSONException e) { e.printStackTrace(); return null; } } */ + + /** + * @param fullUrl ... + * @return the list of POI + */ + public ArrayList<POI> getThem(String fullUrl) { + // for local debug: fullUrl = "http://10.0.2.2/flickr_mockup.json"; + log.debug("FlickrPOIProvider:get:" + fullUrl); + String jString = BonusPackHelper.requestStringFromUrl(fullUrl); + if (jString == null) { + log.error("FlickrPOIProvider: request failed."); + return null; + } + try { + JSONObject jRoot = new JSONObject(jString); + JSONObject jPhotos = jRoot.getJSONObject("photos"); + JSONArray jPhotoArray = jPhotos.getJSONArray("photo"); + int n = jPhotoArray.length(); + ArrayList<POI> pois = new ArrayList<POI>(n); + for (int i = 0; i < n; i++) { + JSONObject jPhoto = jPhotoArray.getJSONObject(i); + + String photoId = jPhoto.getString("id"); + if (mPrevious != null && mPrevious.containsKey(photoId)) + continue; + + POI poi = new POI(POI.POI_SERVICE_FLICKR); + poi.location = new GeoPoint( + jPhoto.getDouble("latitude"), + jPhoto.getDouble("longitude")); + poi.id = photoId; //Long.parseLong(photoId); + poi.type = jPhoto.getString("title"); + poi.thumbnailPath = jPhoto.getString("url_sq"); + String owner = jPhoto.getString("owner"); + // the default flickr link viewer doesnt work with mobile browsers... + // poi.url = "http://www.flickr.com/photos/" + owner + "/" + photoId + "/sizes/o/in/photostream/"; + + poi.url = String.format(PHOTO_URL, owner, photoId); + + pois.add(poi); + } + // int total = jPhotos.getInt("total"); + // log.debug(on a total of:" + total); + return pois; + } catch (JSONException e) { + e.printStackTrace(); + return null; + } + } + + /** + * @param boundingBox ... + * @param maxResults ... + * @return list of POI, Flickr photos inside the bounding box. + * Null if + * technical issue. + */ + public ArrayList<POI> getPOIInside(BoundingBox boundingBox, String query, int maxResults) { + String url = getUrlInside(boundingBox, maxResults); + return getThem(url); + } + + HashMap<String, POI> mPrevious; + + public void setPrevious(HashMap<String, POI> previous) { + mPrevious = previous; + } + +} diff --git a/vtm-app/src/org/osmdroid/location/FourSquareProvider.java b/vtm-app/src/org/osmdroid/location/FourSquareProvider.java new file mode 100644 index 00000000..4035bbfd --- /dev/null +++ b/vtm-app/src/org/osmdroid/location/FourSquareProvider.java @@ -0,0 +1,186 @@ +/* + * Copyright 2012 Hannes Janetzek + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.osmdroid.location; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.oscim.core.BoundingBox; +import org.oscim.core.GeoPoint; +import org.osmdroid.utils.BonusPackHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; + +public class FourSquareProvider implements POIProvider { + + final static Logger log = LoggerFactory.getLogger(FourSquareProvider.class); + + // https://developer.foursquare.com/docs/venues/search + // https://developer.foursquare.com/docs/responses/venue + // https://apigee.com/console/foursquare + + protected String mApiKey; + + // private static HashMap<String, Bitmap> mIcons = + // (HashMap<String,Bitmap>)Collections.synchronizedMap(new HashMap<String, Bitmap>()); + + /** + * @param clientSecret the registered API key to give to Flickr service. + * @see "http://www.flickr.com/help/api/" + */ + public FourSquareProvider(String clientId, String clientSecret) { + mApiKey = "client_id=" + clientId + "&client_secret=" + clientSecret; + } + + //"https://api.foursquare.com/v2/venues/search?v=20120321&intent=checkin&ll=53.06,8.8&client_id=ZUN4ZMNZUFT3Z5QQZNMQ3ACPL4OJMBFGO15TYX51D5MHCIL3&client_secret=X1RXCVF4VVSG1Y2FUDQJLKQUC1WF4XXKIMK2STXKACLPDGLY + @SuppressWarnings("deprecation") + private String getUrlInside(BoundingBox boundingBox, String query, int maxResults) { + StringBuffer url = new StringBuffer( + "https://api.foursquare.com/v2/venues/search?v=20120321" + + "&intent=browse" + + "&client_id=ZUN4ZMNZUFT3Z5QQZNMQ3ACPL4OJMBFGO15TYX51D5MHCIL3" + + "&client_secret=X1RXCVF4VVSG1Y2FUDQJLKQUC1WF4XXKIMK2STXKACLPDGLY"); + url.append("&sw="); + url.append(boundingBox.getMinLatitude()); + url.append(','); + url.append(boundingBox.getMinLongitude()); + url.append("&ne="); + url.append(boundingBox.getMaxLatitude()); + url.append(','); + url.append(boundingBox.getMaxLongitude()); + url.append("&limit="); + url.append(maxResults); + if (query != null) + url.append("&query=" + URLEncoder.encode(query)); + return url.toString(); + } + + /** + * @param fullUrl ... + * @return the list of POI + */ + public ArrayList<POI> getThem(String fullUrl) { + // for local debug: fullUrl = "http://10.0.2.2/flickr_mockup.json"; + log.debug("FlickrPOIProvider:get:" + fullUrl); + String jString = BonusPackHelper.requestStringFromUrl(fullUrl); + if (jString == null) { + log.error("FlickrPOIProvider: request failed."); + return null; + } + try { + JSONObject jRoot = new JSONObject(jString); + + JSONObject jResponse = jRoot.getJSONObject("response"); + JSONArray jVenueArray = jResponse.getJSONArray("venues"); + int n = jVenueArray.length(); + ArrayList<POI> pois = new ArrayList<POI>(n); + for (int i = 0; i < n; i++) { + JSONObject jVenue = jVenueArray.getJSONObject(i); + + POI poi = new POI(POI.POI_SERVICE_4SQUARE); + poi.id = jVenue.getString("id"); + poi.type = jVenue.getString("name"); + // poi.url = jVenue.optString("url", null); + poi.url = "https://foursquare.com/v/" + poi.id; + + JSONObject jLocation = jVenue.getJSONObject("location"); + poi.location = new GeoPoint( + jLocation.getDouble("lat"), + jLocation.getDouble("lng")); + poi.description = jLocation.optString("address", null); + + JSONArray jCategories = jVenue.getJSONArray("categories"); + if (jCategories.length() > 0) { + JSONObject jCategory = jCategories.getJSONObject(0); + String icon = jCategory.getJSONObject("icon").getString("prefix"); + poi.thumbnailPath = icon + 44 + ".png"; + poi.category = jCategory.optString("name"); + } + pois.add(poi); + } + + return pois; + } catch (JSONException e) { + e.printStackTrace(); + return null; + } + } + + /** + * @param boundingBox ... + * @param maxResults ... + * @return list of POI, Flickr photos inside the bounding box. + * Null if + * technical issue. + */ + public ArrayList<POI> getPOIInside(BoundingBox boundingBox, String query, int maxResults) { + String url = getUrlInside(boundingBox, query, maxResults); + return getThem(url); + } + + public static void browse(final Context context, POI poi) { + // get the right url from redirect, could also parse the result from querying venueid... + new AsyncTask<POI, Void, String>() { + + @Override + protected String doInBackground(POI... params) { + POI poi = params[0]; + if (poi == null) + return null; + try { + URL url = new URL(poi.url); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setInstanceFollowRedirects(false); + + String redirect = conn.getHeaderField("Location"); + if (redirect != null) { + log.debug(redirect); + return redirect; + } + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + + @Override + protected void onPostExecute(String result) { + if (result == null) + return; + + Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://foursquare.com" + + result)); + context.startActivity(myIntent); + + } + }.execute(poi); + + } +} diff --git a/vtm-app/src/org/osmdroid/location/GeoNamesPOIProvider.java b/vtm-app/src/org/osmdroid/location/GeoNamesPOIProvider.java new file mode 100644 index 00000000..24d09bd0 --- /dev/null +++ b/vtm-app/src/org/osmdroid/location/GeoNamesPOIProvider.java @@ -0,0 +1,215 @@ +package org.osmdroid.location; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.oscim.core.BoundingBox; +import org.oscim.core.GeoPoint; +import org.osmdroid.utils.BonusPackHelper; +import org.osmdroid.utils.HttpConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Locale; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +/** + * POI Provider using GeoNames services. Currently, "find Nearby Wikipedia" and + * "Wikipedia Articles in Bounding Box" services. + * + * @author M.Kergall + * @see "http://www.geonames.org" + */ +public class GeoNamesPOIProvider { + + final static Logger log = LoggerFactory.getLogger(GeoNamesPOIProvider.class); + + protected String mUserName; + + /** + * @param account the registered "username" to give to GeoNames service. + * @see "http://www.geonames.org/login" + */ + public GeoNamesPOIProvider(String account) { + mUserName = account; + } + + private String getUrlCloseTo(GeoPoint p, int maxResults, double maxDistance) { + StringBuffer url = new StringBuffer("http://api.geonames.org/findNearbyWikipediaJSON?"); + url.append("lat=" + p.getLatitude()); + url.append("&lng=" + p.getLongitude()); + url.append("&maxRows=" + maxResults); + url.append("&radius=" + maxDistance); //km + url.append("&lang=" + Locale.getDefault().getLanguage()); + url.append("&username=" + mUserName); + return url.toString(); + } + + private String getUrlInside(BoundingBox boundingBox, int maxResults) { + StringBuffer url = new StringBuffer("http://api.geonames.org/wikipediaBoundingBoxJSON?"); + url.append("south=" + boundingBox.getMinLatitude()); + url.append("&north=" + boundingBox.getMaxLatitude()); + url.append("&west=" + boundingBox.getMinLongitude()); + url.append("&east=" + boundingBox.getMaxLongitude()); + url.append("&maxRows=" + maxResults); + url.append("&lang=" + Locale.getDefault().getLanguage()); + url.append("&username=" + mUserName); + return url.toString(); + } + + /** + * @param fullUrl ... + * @return the list of POI + */ + public ArrayList<POI> getThem(String fullUrl) { + log.debug("GeoNamesPOIProvider:get:" + fullUrl); + String jString = BonusPackHelper.requestStringFromUrl(fullUrl); + if (jString == null) { + log.error("GeoNamesPOIProvider: request failed."); + return null; + } + try { + JSONObject jRoot = new JSONObject(jString); + JSONArray jPlaceIds = jRoot.getJSONArray("geonames"); + int n = jPlaceIds.length(); + ArrayList<POI> pois = new ArrayList<POI>(n); + for (int i = 0; i < n; i++) { + JSONObject jPlace = jPlaceIds.getJSONObject(i); + POI poi = new POI(POI.POI_SERVICE_GEONAMES_WIKIPEDIA); + poi.location = new GeoPoint(jPlace.getDouble("lat"), + jPlace.getDouble("lng")); + poi.category = jPlace.optString("feature"); + poi.type = jPlace.getString("title"); + poi.description = jPlace.optString("summary"); + poi.thumbnailPath = jPlace.optString("thumbnailImg", null); + /* This makes loading too long. Thumbnail loading will be done + * only when needed, with POI.getThumbnail() if + * (poi.mThumbnailPath != null){ poi.mThumbnail = + * BonusPackHelper.loadBitmap(poi.mThumbnailPath); } */ + poi.url = jPlace.optString("wikipediaUrl", null); + if (poi.url != null) + poi.url = "http://" + poi.url; + poi.rank = jPlace.optInt("rank", 0); + //other attributes: distance? + pois.add(poi); + } + log.debug("done"); + return pois; + } catch (JSONException e) { + e.printStackTrace(); + return null; + } + } + + //XML parsing seems 2 times slower than JSON parsing + public ArrayList<POI> getThemXML(String fullUrl) { + log.debug("GeoNamesPOIProvider:get:" + fullUrl); + HttpConnection connection = new HttpConnection(); + connection.doGet(fullUrl); + InputStream stream = connection.getStream(); + if (stream == null) { + return null; + } + GeoNamesXMLHandler handler = new GeoNamesXMLHandler(); + try { + SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); + parser.parse(stream, handler); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + connection.close(); + log.debug("done"); + return handler.mPOIs; + } + + /** + * @param position ... + * @param maxResults ... + * @param maxDistance ... in km. 20 km max for the free service. + * @return list of POI, Wikipedia entries close to the position. Null if + * technical issue. + */ + public ArrayList<POI> getPOICloseTo(GeoPoint position, + int maxResults, double maxDistance) { + String url = getUrlCloseTo(position, maxResults, maxDistance); + return getThem(url); + } + + /** + * @param boundingBox ... + * @param maxResults ... + * @return list of POI, Wikipedia entries inside the bounding box. Null if + * technical issue. + */ + public ArrayList<POI> getPOIInside(BoundingBox boundingBox, int maxResults) { + String url = getUrlInside(boundingBox, maxResults); + return getThem(url); + } +} + +class GeoNamesXMLHandler extends DefaultHandler { + + private String mString; + double mLat, mLng; + POI mPOI; + ArrayList<POI> mPOIs; + + public GeoNamesXMLHandler() { + mPOIs = new ArrayList<POI>(); + } + + @Override + public void startElement(String uri, String localName, String name, + Attributes attributes) { + if (localName.equals("entry")) { + mPOI = new POI(POI.POI_SERVICE_GEONAMES_WIKIPEDIA); + } + mString = new String(); + } + + @Override + public void characters(char[] ch, int start, int length) { + String chars = new String(ch, start, length); + mString = mString.concat(chars); + } + + @Override + public void endElement(String uri, String localName, String name) { + if (localName.equals("lat")) { + mLat = Double.parseDouble(mString); + } else if (localName.equals("lng")) { + mLng = Double.parseDouble(mString); + } else if (localName.equals("feature")) { + mPOI.category = mString; + } else if (localName.equals("title")) { + mPOI.type = mString; + } else if (localName.equals("summary")) { + mPOI.description = mString; + } else if (localName.equals("thumbnailImg")) { + if (mString != null && !mString.equals("")) + mPOI.thumbnailPath = mString; + } else if (localName.equals("wikipediaUrl")) { + if (mString != null && !mString.equals("")) + mPOI.url = "http://" + mString; + } else if (localName.equals("rank")) { + mPOI.rank = Integer.parseInt(mString); + } else if (localName.equals("entry")) { + mPOI.location = new GeoPoint(mLat, mLng); + mPOIs.add(mPOI); + } + } + +} diff --git a/vtm-app/src/org/osmdroid/location/GeocoderNominatim.java b/vtm-app/src/org/osmdroid/location/GeocoderNominatim.java new file mode 100644 index 00000000..6ea92210 --- /dev/null +++ b/vtm-app/src/org/osmdroid/location/GeocoderNominatim.java @@ -0,0 +1,208 @@ +package org.osmdroid.location; + +import android.content.Context; +import android.location.Address; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.osmdroid.utils.BonusPackHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * Implements an equivalent to Android Geocoder class, based on OpenStreetMap + * data and Nominatim API. <br> + * See http://wiki.openstreetmap.org/wiki/Nominatim or + * http://open.mapquestapi.com/nominatim/ + * + * @author M.Kergall + */ +public class GeocoderNominatim { + + final static Logger log = LoggerFactory.getLogger(GeocoderNominatim.class); + + public static final String NOMINATIM_SERVICE_URL = "http://nominatim.openstreetmap.org/"; + public static final String MAPQUEST_SERVICE_URL = "http://open.mapquestapi.com/nominatim/v1/"; + + protected Locale mLocale; + protected String mServiceUrl; + + /** + * @param context ... + * @param locale ... + */ + protected void init(Context context, Locale locale) { + mLocale = locale; + setService(NOMINATIM_SERVICE_URL); //default service + } + + public GeocoderNominatim(Context context, Locale locale) { + init(context, locale); + } + + public GeocoderNominatim(Context context) { + init(context, Locale.getDefault()); + } + + static public boolean isPresent() { + return true; + } + + /** + * Specify the url of the Nominatim service provider to use. Can be one of + * the predefined (NOMINATIM_SERVICE_URL or MAPQUEST_SERVICE_URL), or + * another one, your local instance of Nominatim for instance. + * + * @param serviceUrl ... + */ + public void setService(String serviceUrl) { + mServiceUrl = serviceUrl; + } + + /** + * Build an Android Address object from the Nominatim address in JSON + * format. Current implementation is mainly targeting french addresses, and + * will be quite basic on other countries. + * + * @param jResult ... + * @return ... + * @throws JSONException ... + */ + protected Address buildAndroidAddress(JSONObject jResult) throws JSONException { + Address gAddress = new Address(mLocale); + gAddress.setLatitude(jResult.getDouble("lat")); + gAddress.setLongitude(jResult.getDouble("lon")); + + JSONObject jAddress = jResult.getJSONObject("address"); + + int addressIndex = 0; + if (jAddress.has("road")) { + gAddress.setAddressLine(addressIndex++, jAddress.getString("road")); + gAddress.setThoroughfare(jAddress.getString("road")); + } + if (jAddress.has("suburb")) { + //gAddress.setAddressLine(addressIndex++, jAddress.getString("suburb")); + //not kept => often introduce "noise" in the address. + gAddress.setSubLocality(jAddress.getString("suburb")); + } + if (jAddress.has("postcode")) { + gAddress.setAddressLine(addressIndex++, jAddress.getString("postcode")); + gAddress.setPostalCode(jAddress.getString("postcode")); + } + + if (jAddress.has("city")) { + gAddress.setAddressLine(addressIndex++, jAddress.getString("city")); + gAddress.setLocality(jAddress.getString("city")); + } else if (jAddress.has("town")) { + gAddress.setAddressLine(addressIndex++, jAddress.getString("town")); + gAddress.setLocality(jAddress.getString("town")); + } else if (jAddress.has("village")) { + gAddress.setAddressLine(addressIndex++, jAddress.getString("village")); + gAddress.setLocality(jAddress.getString("village")); + } + + if (jAddress.has("county")) { //France: departement + gAddress.setSubAdminArea(jAddress.getString("county")); + } + if (jAddress.has("state")) { //France: region + gAddress.setAdminArea(jAddress.getString("state")); + } + if (jAddress.has("country")) { + gAddress.setAddressLine(addressIndex++, jAddress.getString("country")); + gAddress.setCountryName(jAddress.getString("country")); + } + if (jAddress.has("country_code")) + gAddress.setCountryCode(jAddress.getString("country_code")); + + /* Other possible OSM tags in Nominatim results not handled yet: subway, + * golf_course, bus_stop, parking,... house, house_number, building + * city_district (13e Arrondissement) road => or highway, ... sub-city + * (like suburb) => locality, isolated_dwelling, hamlet ... + * state_district */ + + return gAddress; + } + + /** + * @param latitude ... + * @param longitude ... + * @param maxResults ... + * @return ... + * @throws IOException ... + */ + public List<Address> getFromLocation(double latitude, double longitude, int maxResults) + throws IOException { + String url = mServiceUrl + + "reverse?" + + "format=json" + + "&accept-language=" + mLocale.getLanguage() + //+ "&addressdetails=1" + + "&lat=" + latitude + + "&lon=" + longitude; + log.debug("GeocoderNominatim::getFromLocation:" + url); + String result = BonusPackHelper.requestStringFromUrl(url); + //log.debug(result); + if (result == null) + throw new IOException(); + try { + JSONObject jResult = new JSONObject(result); + Address gAddress = buildAndroidAddress(jResult); + List<Address> list = new ArrayList<Address>(); + list.add(gAddress); + return list; + } catch (JSONException e) { + throw new IOException(); + } + } + + public List<Address> getFromLocationName(String locationName, int maxResults, + double lowerLeftLatitude, double lowerLeftLongitude, + double upperRightLatitude, double upperRightLongitude) + throws IOException { + String url = mServiceUrl + + "search?" + + "format=json" + + "&accept-language=" + mLocale.getLanguage() + + "&addressdetails=1" + + "&limit=" + maxResults + + "&q=" + URLEncoder.encode(locationName, "UTF-8"); + if (lowerLeftLatitude != 0.0 && lowerLeftLongitude != 0.0) { + //viewbox = left, top, right, bottom: + url += "&viewbox=" + lowerLeftLongitude + + "," + upperRightLatitude + + "," + upperRightLongitude + + "," + lowerLeftLatitude + + "&bounded=1"; + } + log.debug("GeocoderNominatim::getFromLocationName:" + url); + String result = BonusPackHelper.requestStringFromUrl(url); + //log.debug(result); + if (result == null) + throw new IOException(); + try { + JSONArray jResults = new JSONArray(result); + List<Address> list = new ArrayList<Address>(); + for (int i = 0; i < jResults.length(); i++) { + JSONObject jResult = jResults.getJSONObject(i); + Address gAddress = buildAndroidAddress(jResult); + list.add(gAddress); + } + return list; + } catch (JSONException e) { + throw new IOException(); + } + } + + public List<Address> getFromLocationName(String locationName, int maxResults) + throws IOException { + return getFromLocationName(locationName, maxResults, 0.0, 0.0, 0.0, 0.0); + } + +} diff --git a/vtm-app/src/org/osmdroid/location/NominatimPOIProvider.java b/vtm-app/src/org/osmdroid/location/NominatimPOIProvider.java new file mode 100644 index 00000000..09ea46ff --- /dev/null +++ b/vtm-app/src/org/osmdroid/location/NominatimPOIProvider.java @@ -0,0 +1,192 @@ +package org.osmdroid.location; + +import android.graphics.Bitmap; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.oscim.core.BoundingBox; +import org.oscim.core.GeoPoint; +import org.osmdroid.utils.BonusPackHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URLEncoder; +import java.util.ArrayList; + +/** + * POI Provider using Nominatim service. <br> + * See https://wiki.openstreetmap.org/wiki/Nominatim<br> + * and http://open.mapquestapi.com/nominatim/<br> + * + * @author M.Kergall + */ +public class NominatimPOIProvider implements POIProvider { + + final static Logger log = LoggerFactory.getLogger(NominatimPOIProvider.class); + + /* As the doc lacks a lot of features, source code may help: + * https://trac.openstreetmap + * .org/browser/applications/utils/nominatim/website/search.php featuretype= + * to select on feature type (country, city, state, settlement)<br> + * format=jsonv2 to get a place_rank<br> offset= to offset the result ?... + * <br> polygon=1 to get the border of the poi as a polygon<br> nearlat & + * nearlon = ???<br> routewidth/69 and routewidth/30 ???<br> */ + public static final String MAPQUEST_POI_SERVICE = "http://open.mapquestapi.com/nominatim/v1/"; + public static final String NOMINATIM_POI_SERVICE = "http://nominatim.openstreetmap.org/"; + protected String mService; + + public NominatimPOIProvider() { + mService = NOMINATIM_POI_SERVICE; + } + + public void setService(String serviceUrl) { + mService = serviceUrl; + } + + @SuppressWarnings("deprecation") + private StringBuffer getCommonUrl(String type, int maxResults) { + StringBuffer urlString = new StringBuffer(mService); + urlString.append("search?"); + urlString.append("format=json"); + urlString.append("&q=" + URLEncoder.encode(type)); + urlString.append("&limit=" + maxResults); + //urlString.append("&bounded=1"); + // urlString.append("&addressdetails=0"); + return urlString; + } + + private String getUrlInside(BoundingBox bb, String type, int maxResults) { + StringBuffer urlString = getCommonUrl(type, maxResults); + urlString.append("&viewbox=" + bb.getMaxLongitude() + "," + + bb.getMaxLatitude() + "," + + bb.getMinLongitude() + "," + + bb.getMinLatitude()); + return urlString.toString(); + } + + private String getUrlCloseTo(GeoPoint p, String type, + int maxResults, double maxDistance) { + int maxD = (int) (maxDistance * 1E6); + BoundingBox bb = new BoundingBox(p.latitudeE6 + maxD, + p.longitudeE6 + maxD, + p.latitudeE6 - maxD, + p.longitudeE6 - maxD); + return getUrlInside(bb, type, maxResults); + } + + /** + * @param url full URL request + * @return the list of POI, of null if technical issue. + */ + public ArrayList<POI> getThem(String url) { + log.debug("NominatimPOIProvider:get:" + url); + String jString = BonusPackHelper.requestStringFromUrl(url); + if (jString == null) { + log.error("NominatimPOIProvider: request failed."); + return null; + } + try { + JSONArray jPlaceIds = new JSONArray(jString); + int n = jPlaceIds.length(); + ArrayList<POI> pois = new ArrayList<POI>(n); + Bitmap thumbnail = null; + for (int i = 0; i < n; i++) { + JSONObject jPlace = jPlaceIds.getJSONObject(i); + POI poi = new POI(POI.POI_SERVICE_NOMINATIM); + poi.id = jPlace.getString("osm_id"); + // jPlace.optLong("osm_id"); + poi.location = new GeoPoint(jPlace.getDouble("lat"), jPlace.getDouble("lon")); + JSONArray bbox = jPlace.optJSONArray("boundingbox"); + if (bbox != null) { + try { + poi.bbox = new BoundingBox(bbox.getDouble(0), bbox.getDouble(2), + bbox.getDouble(1), bbox.getDouble(3)); + } catch (Exception e) { + log.debug("could not parse " + bbox); + } + //log.debug("bbox " + poi.bbox); + } + poi.category = jPlace.optString("class"); + poi.type = jPlace.getString("type"); + poi.description = jPlace.optString("display_name"); + poi.thumbnailPath = jPlace.optString("icon", null); + + if (i == 0 && poi.thumbnailPath != null) { + //first POI, and we have a thumbnail: load it + thumbnail = BonusPackHelper.loadBitmap(poi.thumbnailPath); + } + poi.thumbnail = thumbnail; + pois.add(poi); + } + return pois; + } catch (JSONException e) { + e.printStackTrace(); + return null; + } + } + + /** + * @param position ... + * @param type an OpenStreetMap feature. See + * http://wiki.openstreetmap.org/wiki/Map_Features or + * http://code.google.com/p/osmbonuspack/source/browse/trunk/ + * OSMBonusPackDemo/res/values/poi_tags.xml + * @param maxResults the maximum number of POI returned. Note that in any case, + * Nominatim will have an absolute maximum of 100. + * @param maxDistance to the position, in degrees. Note that it is used to build a + * bounding box around the position, not a circle. + * @return the list of POI, null if technical issue. + */ + public ArrayList<POI> getPOICloseTo(GeoPoint position, String type, + int maxResults, double maxDistance) { + String url = getUrlCloseTo(position, type, maxResults, maxDistance); + return getThem(url); + } + + /** + * @param boundingBox ... + * @param type OpenStreetMap feature + * @param maxResults ... + * @return list of POIs, null if technical issue. + */ + public ArrayList<POI> getPOIInside(BoundingBox boundingBox, String type, int maxResults) { + String url = getUrlInside(boundingBox, type, maxResults); + return getThem(url); + } + + public ArrayList<POI> getPOI(String query, int maxResults) { + String url = getCommonUrl(query, maxResults).toString(); + return getThem(url); + } + + /** + * @param path Warning: a long path may cause a failure due to the url to be + * too long. Using a simplified route may help (see + * Road.getRouteLow()). + * @param type OpenStreetMap feature + * @param maxResults ... + * @param maxWidth to the path. Certainly not in degrees. Probably in km. + * @return list of POIs, null if technical issue. + */ + public ArrayList<POI> getPOIAlong(ArrayList<GeoPoint> path, String type, + int maxResults, double maxWidth) { + StringBuffer urlString = getCommonUrl(type, maxResults); + urlString.append("&routewidth=" + maxWidth); + urlString.append("&route="); + boolean isFirst = true; + for (GeoPoint p : path) { + if (isFirst) + isFirst = false; + else + urlString.append(","); + String lat = Double.toString(p.getLatitude()); + lat = lat.substring(0, Math.min(lat.length(), 7)); + String lon = Double.toString(p.getLongitude()); + lon = lon.substring(0, Math.min(lon.length(), 7)); + urlString.append(lat + "," + lon); + //limit the size of url as much as possible, as post method is not supported. + } + return getThem(urlString.toString()); + } +} diff --git a/vtm-app/src/org/osmdroid/location/OverpassPOIProvider.java b/vtm-app/src/org/osmdroid/location/OverpassPOIProvider.java new file mode 100644 index 00000000..b9bade53 --- /dev/null +++ b/vtm-app/src/org/osmdroid/location/OverpassPOIProvider.java @@ -0,0 +1,67 @@ +package org.osmdroid.location; + +import org.oscim.core.BoundingBox; +import org.oscim.core.GeoPoint; +import org.oscim.core.Tag; +import org.oscim.core.osm.OsmData; +import org.oscim.core.osm.OsmNode; +import org.oscim.utils.osmpbf.OsmPbfReader; +import org.osmdroid.utils.HttpConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +public class OverpassPOIProvider implements POIProvider { + + final static Logger log = LoggerFactory + .getLogger(OverpassPOIProvider.class); + + public static final String TAG_KEY_WEBSITE = "website".intern(); + + @Override + public List<POI> getPOIInside(BoundingBox boundingBox, String query, + int maxResults) { + HttpConnection connection = new HttpConnection(); + boundingBox.toString(); + + String q = "node[\"amenity\"~\"^restaurant$|^pub$\"](" + + boundingBox.format() + ");out 100;"; + String url = "http://city.informatik.uni-bremen.de/oapi/pbf?data="; + String encoded; + try { + encoded = URLEncoder.encode(q, "utf-8"); + } catch (UnsupportedEncodingException e1) { + e1.printStackTrace(); + return null; + } + log.debug("request " + url + encoded); + connection.doGet(url + encoded); + OsmData osmData = OsmPbfReader.process(connection.getStream()); + ArrayList<POI> pois = new ArrayList<POI>(osmData.getNodes().size()); + + for (OsmNode n : osmData.getNodes()) { + POI p = new POI(POI.POI_SERVICE_4SQUARE); + p.id = Long.toString(n.id); + + p.location = new GeoPoint(n.lat, n.lon); + Tag t; + + if ((t = n.tags.get(Tag.KEY_NAME)) != null) + p.description = t.value; + + if ((t = n.tags.get(Tag.KEY_AMENITY)) != null) + p.type = t.value; + + if ((t = n.tags.get(TAG_KEY_WEBSITE)) != null) { + log.debug(p.description + " " + t.value); + p.url = t.value; + } + pois.add(p); + } + return pois; + } +} diff --git a/vtm-app/src/org/osmdroid/location/POI.java b/vtm-app/src/org/osmdroid/location/POI.java new file mode 100644 index 00000000..c796c9ff --- /dev/null +++ b/vtm-app/src/org/osmdroid/location/POI.java @@ -0,0 +1,205 @@ +package org.osmdroid.location; + +import android.graphics.Bitmap; +import android.os.AsyncTask; +import android.view.View; +import android.widget.ImageView; + +import org.oscim.app.R; +import org.oscim.core.BoundingBox; +import org.oscim.core.GeoPoint; +import org.osmdroid.utils.BonusPackHelper; + +/** + * Point of Interest. Exact content may depend of the POI provider used. + * + * @author M.Kergall + * @see NominatimPOIProvider + * @see GeoNamesPOIProvider + */ +public class POI { + + /** + * IDs of POI services + */ + public static int POI_SERVICE_NOMINATIM = 100; + public static int POI_SERVICE_GEONAMES_WIKIPEDIA = 200; + public static int POI_SERVICE_FLICKR = 300; + public static int POI_SERVICE_PICASA = 400; + public static int POI_SERVICE_4SQUARE = 500; + + /** + * Identifies the service provider of this POI. + */ + public int serviceId; + /** + * Nominatim: OSM ID. GeoNames: 0 + */ + public String id; + /** + * location of the POI + */ + public GeoPoint location; + public BoundingBox bbox; + /** + * Nominatim "class", GeoNames "feature" + */ + public String category; + /** + * type or title + */ + public String type; + /** + * can be the name, the address, a short description + */ + public String description; + /** + * url of the thumbnail. Null if none + */ + public String thumbnailPath; + /** + * the thumbnail itself. Null if none + */ + public Bitmap thumbnail; + /** + * url to a more detailed information page about this POI. Null if none + */ + public String url; + /** + * popularity of this POI, from 1 (lowest) to 100 (highest). 0 if not + * defined. + */ + public int rank; + + /** + * number of attempts to load the thumbnail that have failed + */ + protected int mThumbnailLoadingFailures; + + public POI(int serviceId) { + this.serviceId = serviceId; + // lets all other fields empty or null. That's fine. + } + + protected static int MAX_LOADING_ATTEMPTS = 2; + + /** + * @return the POI thumbnail as a Bitmap, if any. If not done yet, it will + * load the POI thumbnail from its url (in thumbnailPath field). + */ + public Bitmap getThumbnail() { + if (thumbnail == null && thumbnailPath != null) { + thumbnail = BonusPackHelper.loadBitmap(thumbnailPath); + if (thumbnail == null) { + mThumbnailLoadingFailures++; + if (mThumbnailLoadingFailures >= MAX_LOADING_ATTEMPTS) { + // this path really doesn't work, "kill" it for next calls: + thumbnailPath = null; + } + } + } + return thumbnail; + } + + // http://stackoverflow.com/questions/7729133/using-asynctask-to-load-images-in-listview + // TODO see link, there might be a better solution + + /** + * Fetch the thumbnail from its url on a thread. + * + * @param imageView to update once the thumbnail is retrieved, or to hide if no + * thumbnail. + */ + public void fetchThumbnail(final ImageView imageView) { + if (thumbnail != null) { + imageView.setImageBitmap(thumbnail); + imageView.setVisibility(View.VISIBLE); + } else if (thumbnailPath != null) { + imageView.setImageResource(R.drawable.ic_empty); + imageView.setVisibility(View.VISIBLE); + new ThumbnailTask(imageView).execute(imageView); + } else { + imageView.setVisibility(View.GONE); + } + } + + class ThumbnailTask extends AsyncTask<ImageView, Void, ImageView> { + + public ThumbnailTask(ImageView iv) { + iv.setTag(thumbnailPath); + } + + @Override + protected ImageView doInBackground(ImageView... params) { + getThumbnail(); + return params[0]; + } + + @Override + protected void onPostExecute(ImageView iv) { + if (iv == null || thumbnail == null) + return; + if (thumbnailPath.equals(iv.getTag().toString())) + iv.setImageBitmap(thumbnail); + } + } + + // --- Parcelable implementation + + // @Override + // public int describeContents() { + // return 0; + // } + + // @Override + // public void writeToParcel(Parcel out, int flags) { + // out.writeInt(serviceId); + // out.writeString(id); + // out.writeParcelable(location, 0); + // out.writeString(category); + // out.writeString(type); + // out.writeString(description); + // out.writeString(thumbnailPath); + // out.writeParcelable(thumbnail, 0); + // out.writeString(url); + // out.writeInt(rank); + // out.writeInt(mThumbnailLoadingFailures); + // } + // + // public static final Parcelable.Creator<POI> CREATOR = new Parcelable.Creator<POI>() { + // @Override + // public POI createFromParcel(Parcel in) { + // POI poi = new POI(in.readInt()); + // poi.id = in.readString(); + // poi.location = in.readParcelable(GeoPoint.class.getClassLoader()); + // poi.category = in.readString(); + // poi.type = in.readString(); + // poi.description = in.readString(); + // poi.thumbnailPath = in.readString(); + // poi.thumbnail = in.readParcelable(Bitmap.class.getClassLoader()); + // poi.url = in.readString(); + // poi.rank = in.readInt(); + // poi.mThumbnailLoadingFailures = in.readInt(); + // return poi; + // } + // + // @Override + // public POI[] newArray(int size) { + // return new POI[size]; + // } + // }; + + // private POI(Parcel in) { + // serviceId = in.readInt(); + // id = in.readLong(); + // location = in.readParcelable(GeoPoint.class.getClassLoader()); + // category = in.readString(); + // type = in.readString(); + // description = in.readString(); + // thumbnailPath = in.readString(); + // thumbnail = in.readParcelable(Bitmap.class.getClassLoader()); + // url = in.readString(); + // rank = in.readInt(); + // mThumbnailLoadingFailures = in.readInt(); + // } +} diff --git a/vtm-app/src/org/osmdroid/location/POIProvider.java b/vtm-app/src/org/osmdroid/location/POIProvider.java new file mode 100644 index 00000000..11c01446 --- /dev/null +++ b/vtm-app/src/org/osmdroid/location/POIProvider.java @@ -0,0 +1,10 @@ +package org.osmdroid.location; + +import org.oscim.core.BoundingBox; + +import java.util.List; + +public interface POIProvider { + + public List<POI> getPOIInside(BoundingBox boundingBox, String query, int maxResults); +} diff --git a/vtm-app/src/org/osmdroid/location/PicasaPOIProvider.java b/vtm-app/src/org/osmdroid/location/PicasaPOIProvider.java new file mode 100644 index 00000000..9552e2d3 --- /dev/null +++ b/vtm-app/src/org/osmdroid/location/PicasaPOIProvider.java @@ -0,0 +1,161 @@ +package org.osmdroid.location; + +import org.oscim.core.BoundingBox; +import org.oscim.core.GeoPoint; +import org.osmdroid.utils.HttpConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +/** + * POI Provider using Picasa service. + * + * @author M.Kergall + * @see "https://developers.google.com/picasa-web/docs/2.0/reference" + */ +public class PicasaPOIProvider implements POIProvider { + + final static Logger log = LoggerFactory.getLogger(PicasaPOIProvider.class); + + String mAccessToken; + + /** + * @param accessToken the account to give to the service. Null for public access. + * @see "https://developers.google.com/picasa-web/docs/2.0/developers_guide_protocol#CreatingAccount" + */ + public PicasaPOIProvider(String accessToken) { + mAccessToken = accessToken; + } + + @SuppressWarnings("deprecation") + private String getUrlInside(BoundingBox boundingBox, int maxResults, String query) { + StringBuffer url = new StringBuffer("http://picasaweb.google.com/data/feed/api/all?"); + url.append("bbox=" + boundingBox.getMinLongitude()); + url.append("," + boundingBox.getMinLatitude()); + url.append("," + boundingBox.getMaxLongitude()); + url.append("," + boundingBox.getMaxLatitude()); + url.append("&max-results=" + maxResults); + url.append("&thumbsize=64c"); //thumbnail size: 64, cropped. + url.append("&fields=openSearch:totalResults,entry(summary,media:group/media:thumbnail,media:group/media:title,gphoto:*,georss:where,link)"); + if (query != null) + url.append("&q=" + URLEncoder.encode(query)); + if (mAccessToken != null) { + //TODO: warning: not tested... + url.append("&access_token=" + mAccessToken); + } + return url.toString(); + } + + public ArrayList<POI> getThem(String fullUrl) { + log.debug("PicasaPOIProvider:get:" + fullUrl); + HttpConnection connection = new HttpConnection(); + connection.doGet(fullUrl); + InputStream stream = connection.getStream(); + if (stream == null) { + return null; + } + PicasaXMLHandler handler = new PicasaXMLHandler(); + try { + SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); + parser.getXMLReader().setFeature("http://xml.org/sax/features/namespaces", false); + parser.getXMLReader() + .setFeature("http://xml.org/sax/features/namespace-prefixes", true); + parser.parse(stream, handler); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + connection.close(); + if (handler.mPOIs != null) + log.debug("done:" + handler.mPOIs.size() + " got on a total of:" + + handler.mTotalResults); + return handler.mPOIs; + } + + /** + * @param boundingBox ... + * @param maxResults ... + * @param query - optional - full-text query string. Searches the title, + * caption and tags for the specified string value. + * @return list of POI, Picasa photos inside the bounding box. Null if + * technical issue. + */ + public List<POI> getPOIInside(BoundingBox boundingBox, String query, int maxResults) { + String url = getUrlInside(boundingBox, maxResults, query); + return getThem(url); + } +} + +class PicasaXMLHandler extends DefaultHandler { + + private String mString; + double mLat, mLng; + POI mPOI; + ArrayList<POI> mPOIs; + int mTotalResults; + + public PicasaXMLHandler() { + mPOIs = new ArrayList<POI>(); + } + + @Override + public void startElement(String uri, String localName, String qName, + Attributes attributes) { + if (qName.equals("entry")) { + mPOI = new POI(POI.POI_SERVICE_PICASA); + } else if (qName.equals("media:thumbnail")) { + mPOI.thumbnailPath = attributes.getValue("url"); + } else if (qName.equals("link")) { + String rel = attributes.getValue("rel"); + if ("http://schemas.google.com/photos/2007#canonical".equals(rel)) { + mPOI.url = attributes.getValue("href"); + } + } + mString = new String(); + } + + @Override + public void characters(char[] ch, int start, int length) { + String chars = new String(ch, start, length); + mString = mString.concat(chars); + } + + @Override + public void endElement(String uri, String localName, String qName) { + if (qName.equals("gml:pos")) { + String[] coords = mString.split(" "); + mLat = Double.parseDouble(coords[0]); + mLng = Double.parseDouble(coords[1]); + } else if (qName.equals("gphoto:id")) { + mPOI.id = mString; + } else if (qName.equals("media:title")) { + mPOI.type = mString; + } else if (qName.equals("summary")) { + mPOI.description = mString; + } else if (qName.equals("gphoto:albumtitle")) { + mPOI.category = mString; + } else if (qName.equals("entry")) { + mPOI.location = new GeoPoint(mLat, mLng); + mPOIs.add(mPOI); + mPOI = null; + } else if (qName.equals("openSearch:totalResults")) { + mTotalResults = Integer.parseInt(mString); + } + } + +} diff --git a/vtm-app/src/org/osmdroid/overlays/DefaultInfoWindow.java b/vtm-app/src/org/osmdroid/overlays/DefaultInfoWindow.java new file mode 100644 index 00000000..750ff184 --- /dev/null +++ b/vtm-app/src/org/osmdroid/overlays/DefaultInfoWindow.java @@ -0,0 +1,99 @@ +package org.osmdroid.overlays; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import org.oscim.android.MapView; +import org.oscim.app.App; +import org.osmdroid.utils.BonusPackHelper; + +/** + * Default implementation of InfoWindow. It handles a text and a description. It + * also handles optionally a sub-description and an image. Clicking on the + * bubble will close it. + * + * @author M.Kergall + */ +public class DefaultInfoWindow extends InfoWindow { + + // resource ids + private static int mTitleId = 0, mDescriptionId = 0, mSubDescriptionId = 0, mImageId = 0; + + private static void setResIds(Context context) { + // get application package name + String packageName = context.getPackageName(); + Resources res = context.getResources(); + + mTitleId = res.getIdentifier("id/bubble_title", null, packageName); + mDescriptionId = res.getIdentifier("id/bubble_description", null, packageName); + mSubDescriptionId = res.getIdentifier("id/bubble_subdescription", null, packageName); + mImageId = res.getIdentifier("id/bubble_image", null, packageName); + + if (mTitleId == 0 || mDescriptionId == 0) { + Log.e(BonusPackHelper.LOG_TAG, "DefaultInfoWindow: unable to get res ids in " + + packageName); + } + } + + public DefaultInfoWindow(int layoutResId, MapView mapView) { + super(layoutResId, mapView); + + if (mTitleId == 0) + setResIds(App.activity); + + // default behaviour: close it when clicking on the bubble: + mView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + close(); + } + }); + } + + @Override + public void onOpen(ExtendedMarkerItem item) { + String title = item.getTitle(); + if (title == null) + title = ""; + + ((TextView) mView.findViewById(mTitleId)).setText(title); + + String snippet = item.getDescription(); + if (snippet == null) + snippet = ""; + + ((TextView) mView.findViewById(mDescriptionId)).setText(snippet); + + // handle sub-description, hidding or showing the text view: + TextView subDescText = (TextView) mView.findViewById(mSubDescriptionId); + String subDesc = item.getSubDescription(); + if (subDesc != null && !("".equals(subDesc))) { + subDescText.setText(subDesc); + subDescText.setVisibility(View.VISIBLE); + } else { + subDescText.setVisibility(View.GONE); + } + + // handle image + ImageView imageView = (ImageView) mView.findViewById(mImageId); + Drawable image = item.getImage(); + if (image != null) { + // or setBackgroundDrawable(image)? + imageView.setImageDrawable(image); + imageView.setVisibility(View.VISIBLE); + } else + imageView.setVisibility(View.GONE); + + } + + @Override + public void onClose() { + // by default, do nothing + } + +} diff --git a/vtm-app/src/org/osmdroid/overlays/ExtendedMarkerItem.java b/vtm-app/src/org/osmdroid/overlays/ExtendedMarkerItem.java new file mode 100644 index 00000000..fcde0141 --- /dev/null +++ b/vtm-app/src/org/osmdroid/overlays/ExtendedMarkerItem.java @@ -0,0 +1,123 @@ +package org.osmdroid.overlays; + +import android.graphics.drawable.Drawable; + +import org.oscim.core.GeoPoint; +import org.oscim.layers.marker.MarkerItem; +import org.oscim.map.Map; + +/** + * An OverlayItem to use in ItemizedOverlayWithBubble<br> + * - more complete: can contain an image and a sub-description that will be + * displayed in the bubble, <br> + * - and flexible: attributes are modifiable<br> + * Known Issues:<br> + * - Bubble offset is not perfect on h&xhdpi resolutions, due to an osmdroid + * issue on marker drawing<br> + * - Bubble offset is at 0 when using the default marker => set the marker on + * each item!<br> + * + * @author M.Kergall + * @see ItemizedOverlayWithBubble + */ +public class ExtendedMarkerItem extends MarkerItem { + + // now, they are modifiable + private String mTitle, mDescription; + // now, they are modifiable + // a third field that can be displayed in + // the infowindow, on a third line + // that will be shown in the infowindow. + //unfortunately, this is not so simple... + private String mSubDescription; + private Drawable mImage; + private Object mRelatedObject; // reference to an object (of any kind) + // linked to this item. + + public ExtendedMarkerItem(String aTitle, String aDescription, GeoPoint aGeoPoint) { + super(aTitle, aDescription, aGeoPoint); + mTitle = aTitle; + mDescription = aDescription; + mSubDescription = null; + mImage = null; + mRelatedObject = null; + } + + public void setTitle(String aTitle) { + mTitle = aTitle; + } + + public void setDescription(String aDescription) { + mDescription = aDescription; + } + + public void setSubDescription(String aSubDescription) { + mSubDescription = aSubDescription; + } + + public void setImage(Drawable anImage) { + mImage = anImage; + } + + public void setRelatedObject(Object o) { + mRelatedObject = o; + } + + @Override + public String getTitle() { + return mTitle; + } + + public String getDescription() { + return mDescription; + } + + public String getSubDescription() { + return mSubDescription; + } + + public Drawable getImage() { + return mImage; + } + + public Object getRelatedObject() { + return mRelatedObject; + } + + /** + * Populates this bubble with all item info: + * <ul> + * title and description in any case, + * </ul> + * <ul> + * image and sub-description if any. + * </ul> + * and centers the map on the item. <br> + * + * @param bubble ... + * @param map ... + */ + public void showBubble(InfoWindow bubble, Map map) { + // offset the bubble to be top-centered on the marker: + // Drawable marker = getMarker(0 /* OverlayItem.ITEM_STATE_FOCUSED_MASK */); + // int markerWidth = 0, markerHeight = 0; + // if (marker != null) { + // markerWidth = marker.getIntrinsicWidth(); + // markerHeight = marker.getIntrinsicHeight(); + // } // else... we don't have the default marker size => don't user default + // // markers!!! + // Point markerH = getHotspot(getMarkerHotspot(), markerWidth, markerHeight); + // Point bubbleH = getHotspot(HotspotPlace.TOP_CENTER, markerWidth, markerHeight); + // bubbleH.offset(-markerH.x, -markerH.y); + // + // bubble.open(this, bubbleH.x, bubbleH.y); + // OverlayMarker marker = getMarker(); + // PointF hotspot = marker.getHotspot(); + // Bitmap b = marker.getBitmap(); + + //bubble.open(this, (int)(-b.getWidth() * hotspot.x), (int)(-b.getHeight())); + //bubble.open(this, 0, (int)(b.getHeight())); + + bubble.open(this, 0, 0); + } +} diff --git a/vtm-app/src/org/osmdroid/overlays/InfoWindow.java b/vtm-app/src/org/osmdroid/overlays/InfoWindow.java new file mode 100644 index 00000000..9592b8d9 --- /dev/null +++ b/vtm-app/src/org/osmdroid/overlays/InfoWindow.java @@ -0,0 +1,135 @@ +package org.osmdroid.overlays; + +// TODO composite view as texture overlay and only allow one bubble at a time. + +import android.content.Context; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RelativeLayout; + +import org.oscim.android.MapView; + +/** + * View that can be displayed on an OSMDroid map, associated to a GeoPoint. + * Typical usage: cartoon-like bubbles displayed when clicking an overlay item. + * It mimics the InfoWindow class of Google Maps JavaScript API V3. Main + * differences are: + * <ul> + * <li>Structure and content of the view is let to the responsibility of the + * caller.</li> + * <li>The same InfoWindow can be associated to many items.</li> + * </ul> + * Known issues: + * <ul> + * <li>It disappears when zooming in/out (osmdroid issue #259 on osmdroid 3.0.8, + * should be fixed in next version).</li> + * <li>The window is displayed "above" the marker, so the queue of the bubble + * can hide the marker.</li> + * </ul> + * This is an abstract class. + * + * @author M.Kergall + * @see DefaultInfoWindow + */ +public abstract class InfoWindow { + + protected View mView; + protected boolean mIsVisible = false; + protected RelativeLayout mLayout; + private android.widget.RelativeLayout.LayoutParams mLayoutPos; + + private MapView mMap; + + /** + * @param layoutResId the id of the view resource. + * @param mapView the mapview on which is hooked the view + */ + public InfoWindow(int layoutResId, MapView mapView) { + ViewGroup parent = (ViewGroup) mapView.getParent(); + Context context = mapView.getContext(); + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mView = inflater.inflate(layoutResId, parent, false); + + RelativeLayout.LayoutParams rlp = + new RelativeLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.MATCH_PARENT); + mLayout = new RelativeLayout(context); + mLayout.setWillNotDraw(true); + mLayout.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); + mLayout.setLayoutParams(rlp); + mLayoutPos = rlp; + mView.setDrawingCacheEnabled(true); + mLayout.addView(mView); + + mIsVisible = false; + mLayout.setVisibility(View.GONE); + mMap = mapView; + + parent.addView(mLayout); + } + + /** + * Returns the Android view. This allows to set its content. + * + * @return the Android view + */ + public View getView() { + return (mView); + } + + private int mHeight; + + /** + * open the window at the specified position. + * + * @param item the item on which is hooked the view + * @param offsetX (&offsetY) the offset of the view to the position, in pixels. + * This allows to offset the view from the marker position. + * @param offsetY ... + */ + public void open(ExtendedMarkerItem item, int offsetX, int offsetY) { + + onOpen(item); + close(); + + mView.buildDrawingCache(); + + mHeight = mMap.getHeight(); + mLayout.setVisibility(View.VISIBLE); + mIsVisible = true; + + } + + public void position(int x, int y) { + RelativeLayout.LayoutParams rlp = mLayoutPos; + rlp.leftMargin = x; + rlp.rightMargin = -x; + rlp.topMargin = y; + rlp.bottomMargin = mHeight / 2 - y; + mLayout.setLayoutParams(rlp); + mLayout.requestLayout(); + } + + public void close() { + + if (mIsVisible) { + mIsVisible = false; + mLayout.setVisibility(View.GONE); + onClose(); + } + } + + public boolean isOpen() { + return mIsVisible; + } + + // Abstract methods to implement: + public abstract void onOpen(ExtendedMarkerItem item); + + public abstract void onClose(); + +} diff --git a/vtm-app/src/org/osmdroid/overlays/ItemizedOverlayWithBubble.java b/vtm-app/src/org/osmdroid/overlays/ItemizedOverlayWithBubble.java new file mode 100644 index 00000000..2a68ab4f --- /dev/null +++ b/vtm-app/src/org/osmdroid/overlays/ItemizedOverlayWithBubble.java @@ -0,0 +1,195 @@ +package org.osmdroid.overlays; + +import android.content.Context; +import android.util.Log; + +import org.oscim.app.App; +import org.oscim.core.GeoPoint; +import org.oscim.core.MapPosition; +import org.oscim.core.Point; +import org.oscim.event.Event; +import org.oscim.event.MotionEvent; +import org.oscim.layers.marker.ItemizedLayer; +import org.oscim.layers.marker.MarkerItem; +import org.oscim.layers.marker.MarkerSymbol; +import org.oscim.map.Map; +import org.osmdroid.utils.BonusPackHelper; + +import java.util.List; + +/** + * An itemized overlay with an InfoWindow or "bubble" which opens when the user + * taps on an overlay item, and displays item attributes. <br> + * Items must be ExtendedMarkerItem. <br> + * + * @param <Item> ... + * @author M.Kergall + * @see ExtendedMarkerItem + * @see InfoWindow + */ +public class ItemizedOverlayWithBubble<Item extends MarkerItem> extends + ItemizedLayer<Item> implements + ItemizedLayer.OnItemGestureListener<Item>, Map.UpdateListener { + + /* only one for all items of this overlay => one at a time */ + protected InfoWindow mBubble; + + /* the item currently showing the bubble. Null if none. */ + protected MarkerItem mItemWithBubble; + + static int layoutResId = 0; + + public ItemizedOverlayWithBubble(Map map, Context context, + MarkerSymbol marker, List<Item> list, InfoWindow bubble) { + super(map, list, marker, null); + + if (bubble != null) { + mBubble = bubble; + } else { + // build default bubble: + String packageName = context.getPackageName(); + if (layoutResId == 0) { + layoutResId = context.getResources().getIdentifier( + "layout/bonuspack_bubble", + null, + packageName); + if (layoutResId == 0) + Log.e(BonusPackHelper.LOG_TAG, + "ItemizedOverlayWithBubble: layout/bonuspack_bubble not found in " + + packageName); + } + mBubble = new DefaultInfoWindow(layoutResId, App.view); + } + + mItemWithBubble = null; + mOnItemGestureListener = this; + } + + public ItemizedOverlayWithBubble(Map map, Context context, + MarkerSymbol marker, List<Item> aList) { + this(map, context, marker, aList, null); + } + + @Override + public boolean onItemLongPress(int index, MarkerItem item) { + if (mBubble.isOpen()) + hideBubble(); + else + showBubble(index); + return false; + } + + @Override + public boolean onItemSingleTapUp(int index, MarkerItem item) { + showBubble(index); + + return true; + } + + private final Point mTmpPoint = new Point(); + + @Override + protected boolean activateSelectedItems(MotionEvent event, ActiveItem task) { + boolean hit = super.activateSelectedItems(event, task); + + if (!hit) + hideBubble(); + + return hit; + } + + @Override + public void onMapEvent(Event e, MapPosition mapPosition) { + if (mBubble.isOpen()) { + GeoPoint gp = mItemWithBubble.getPoint(); + + Point p = mTmpPoint; + mMap.viewport().toScreenPoint(gp, p); + + mBubble.position((int) p.x, (int) p.y); + } + } + + void showBubble(int index) { + showBubbleOnItem(index); + } + + /** + * Opens the bubble on the item. For each ItemizedOverlay, only one bubble + * is opened at a time. If you want more bubbles opened simultaneously, use + * many ItemizedOverlays. + * + * @param index of the overlay item to show + */ + @SuppressWarnings("unchecked") + public void showBubbleOnItem(int index) { + ExtendedMarkerItem item = (ExtendedMarkerItem) (mItemList.get(index)); + mItemWithBubble = item; + if (item != null) { + item.showBubble(mBubble, (Map) mMap); + + mMap.animator().animateTo(item.geoPoint); + + mMap.updateMap(true); + setFocus((Item) item); + } + } + + /** + * Close the bubble (if it's opened). + */ + public void hideBubble() { + mBubble.close(); + mItemWithBubble = null; + } + + // @Override + // public boolean onSingleTapUp(final MotionEvent event) { + // boolean handled = super.onSingleTapUp(event); + // if (!handled) + // hideBubble(); + // return handled; + // } + // + // @Override + // protected boolean onSingleTapUpHelper(final int index, final Item item) { + // showBubbleOnItem(index); + // return true; + // } + + /** + * @return the item currenty showing the bubble, or null if none. + */ + public MarkerItem getBubbledItem() { + if (mBubble.isOpen()) + return mItemWithBubble; + + return null; + } + + /** + * @return the index of the item currenty showing the bubble, or -1 if none. + */ + public int getBubbledItemId() { + MarkerItem item = getBubbledItem(); + if (item == null) + return -1; + + return mItemList.indexOf(item); + } + + @Override + public boolean removeItem(final Item item) { + boolean result = super.removeItem(item); + if (mItemWithBubble == item) { + hideBubble(); + } + return result; + } + + @Override + public void removeAllItems() { + super.removeAllItems(); + hideBubble(); + } +} diff --git a/vtm-app/src/org/osmdroid/overlays/MapEventsReceiver.java b/vtm-app/src/org/osmdroid/overlays/MapEventsReceiver.java new file mode 100644 index 00000000..d4381c33 --- /dev/null +++ b/vtm-app/src/org/osmdroid/overlays/MapEventsReceiver.java @@ -0,0 +1,34 @@ +package org.osmdroid.overlays; + +import org.oscim.core.GeoPoint; + +/** + * Interface for objects that need to handle map events thrown by a + * MapEventsOverlay. + * + * @author M.Kergall + */ +public interface MapEventsReceiver { + + /** + * @param p the position where the event occurred. + * @return true if the event has been "consumed" and should not be handled + * by other objects. + */ + boolean singleTapUpHelper(GeoPoint p); + + /** + * @param p the position where the event occurred. + * @return true if the event has been "consumed" and should not be handled + * by other objects. + */ + boolean longPressHelper(GeoPoint p); + + /** + * @param p1 p2 + * the position where the event occurred for 2 finger. + * @return true if the event has been "consumed" and should not be handled + * by other objects. + */ + boolean longPressHelper(GeoPoint p1, GeoPoint p2); +} diff --git a/vtm-app/src/org/osmdroid/routing/Route.java b/vtm-app/src/org/osmdroid/routing/Route.java new file mode 100644 index 00000000..eab226cc --- /dev/null +++ b/vtm-app/src/org/osmdroid/routing/Route.java @@ -0,0 +1,255 @@ +package org.osmdroid.routing; + +import org.oscim.core.BoundingBox; +import org.oscim.core.GeoPoint; +import org.osmdroid.routing.provider.GoogleRouteProvider; +import org.osmdroid.routing.provider.MapQuestRouteProvider; +import org.osmdroid.routing.provider.OSRMRouteProvider; +import org.osmdroid.utils.DouglasPeuckerReducer; + +import java.util.ArrayList; +import java.util.List; + +/** + * describes the way to go from a position to an other. Normally returned by a + * call to a Directions API (from MapQuest, GoogleMaps or other) + * + * @author M.Kergall + * @see MapQuestRouteProvider + * @see GoogleRouteProvider + * @see OSRMRouteProvider + */ +public class Route { + //final static Logger log = LoggerFactory.getLogger(Route.class); + + /** + * @see #STATUS_INVALID STATUS_INVALID + * @see #STATUS_OK STATUS_OK + * @see #STATUS_DEFAULT STATUS_DEFAULT + */ + public int status; + + /** + * length of the whole route in km. + */ + public double length; + /** + * duration of the whole trip in sec. + */ + public double duration; + public List<RouteNode> nodes; + /** */ + /** + * there is one leg between each waypoint + */ + public List<RouteLeg> legs; + /** + * full shape: polyline, as an array of GeoPoints + */ + public List<GeoPoint> routeHigh; + /** + * the same, in low resolution (less points) + */ + private List<GeoPoint> routeLow; + /** + * route bounding box + */ + public BoundingBox boundingBox; + + /** + * STATUS_INVALID = route not built + */ + public static final int STATUS_INVALID = 0; + /** + * STATUS_OK = route properly retrieved and built + */ + public static final int STATUS_OK = 1; + /** + * STATUS_DEFAULT = any issue (technical issue, or no possible route) led to + * build a default route + */ + public static final int STATUS_DEFAULT = 2; + + private void init() { + status = STATUS_INVALID; + length = 0.0; + duration = 0.0; + nodes = new ArrayList<RouteNode>(); + routeHigh = new ArrayList<GeoPoint>(); + routeLow = null; + legs = new ArrayList<RouteLeg>(); + boundingBox = null; + } + + public Route() { + init(); + } + + /** + * default constructor when normal loading failed: the route shape only + * contains the waypoints; All distances and times are at 0; there is no + * node; status equals DEFAULT. + * + * @param waypoints ... + */ + public Route(List<GeoPoint> waypoints) { + init(); + int n = waypoints.size(); + for (int i = 0; i < n; i++) { + GeoPoint p = waypoints.get(i); + routeHigh.add(p); + } + for (int i = 0; i < n - 1; i++) { + RouteLeg leg = new RouteLeg(/* i, i+1, mLinks */); + legs.add(leg); + } + boundingBox = BoundingBox.fromGeoPoints(routeHigh); + status = STATUS_DEFAULT; + } + + /** + * @return the route shape in "low resolution" = simplified by around 10 + * factor. + */ + public List<GeoPoint> getRouteLow() { + if (routeLow == null) { + // Simplify the route (divide number of points by around 10): + //int n = routeHigh.size(); + routeLow = DouglasPeuckerReducer.reduceWithTolerance(routeHigh, 1500.0); + //log.debug("route reduced from " + n + " to " + routeLow.size() + // + " points"); + } + return routeLow; + } + + public void setRouteLow(ArrayList<GeoPoint> route) { + routeLow = route; + } + + /** + * @param pLength in km + * @param pDuration in sec + * @return a human-readable length&duration text. + */ + public String getLengthDurationText(double pLength, double pDuration) { + String result; + if (pLength >= 100.0) { + result = (int) (pLength) + " km, "; + } else if (pLength >= 1.0) { + result = Math.round(pLength * 10) / 10.0 + " km, "; + } else { + result = (int) (pLength * 1000) + " m, "; + } + int totalSeconds = (int) pDuration; + int hours = totalSeconds / 3600; + int minutes = (totalSeconds / 60) - (hours * 60); + int seconds = (totalSeconds % 60); + if (hours != 0) { + result += hours + " h "; + } + if (minutes != 0) { + result += minutes + " min"; + } + if (hours == 0 && minutes == 0) { + result += seconds + " s"; + } + return result; + } + + /** + * @param leg leg index, starting from 0. -1 for the whole route + * @return length and duration of the whole route, or of a leg of the route, + * as a String, in a readable format. + */ + public String getLengthDurationText(int leg) { + double len = (leg == -1 ? this.length : legs.get(leg).length); + double dur = (leg == -1 ? this.duration : legs.get(leg).duration); + return getLengthDurationText(len, dur); + } + + protected double distanceLLSquared(GeoPoint p1, GeoPoint p2) { + double deltaLat = p2.latitudeE6 - p1.latitudeE6; + double deltaLon = p2.longitudeE6 - p1.longitudeE6; + return (deltaLat * deltaLat + deltaLon * deltaLon); + } + + /** + * As MapQuest and OSRM doesn't provide legs information, we have to rebuild + * it, using the waypoints and the route nodes. <br> + * Note that MapQuest legs fit well with waypoints, as there is a + * "dedicated" node for each waypoint. But OSRM legs are not precise, as + * there is no node "dedicated" to waypoints. + * + * @param waypoints ... + */ + public void buildLegs(List<GeoPoint> waypoints) { + legs = new ArrayList<RouteLeg>(); + int firstNodeIndex = 0; + // For all intermediate waypoints, search the node closest to the + // waypoint + int w = waypoints.size(); + int n = nodes.size(); + for (int i = 1; i < w - 1; i++) { + GeoPoint waypoint = waypoints.get(i); + double distanceMin = -1.0; + int nodeIndexMin = -1; + for (int j = firstNodeIndex; j < n; j++) { + GeoPoint routePoint = nodes.get(j).location; + double dSquared = distanceLLSquared(routePoint, waypoint); + if (nodeIndexMin == -1 || dSquared < distanceMin) { + distanceMin = dSquared; + nodeIndexMin = j; + } + } + // Build the leg as ending with this closest node: + RouteLeg leg = new RouteLeg(firstNodeIndex, nodeIndexMin, nodes); + legs.add(leg); + firstNodeIndex = nodeIndexMin + 1; // restart next leg from end + } + // Build last leg ending with last node: + RouteLeg lastLeg = new RouteLeg(firstNodeIndex, n - 1, nodes); + legs.add(lastLeg); + } + + // --- Parcelable implementation + + // @Override + // public int describeContents() { + // return 0; + // } + // + // @Override + // public void writeToParcel(Parcel out, int flags) { + // out.writeInt(status); + // out.writeDouble(length); + // out.writeDouble(duration); + // out.writeList(nodes); + // out.writeList(legs); + // out.writeList(routeHigh); + // out.writeParcelable(boundingBox, 0); + // } + // + // public static final Parcelable.Creator<Route> CREATOR = new Parcelable.Creator<Route>() { + // @Override + // public Route createFromParcel(Parcel source) { + // return new Route(source); + // } + // + // @Override + // public Route[] newArray(int size) { + // return new Route[size]; + // } + // }; + // + // @SuppressWarnings("unchecked") + // private Route(Parcel in) { + // status = in.readInt(); + // length = in.readDouble(); + // duration = in.readDouble(); + // + // nodes = in.readArrayList(RouteNode.class.getClassLoader()); + // legs = in.readArrayList(RouteLeg.class.getClassLoader()); + // routeHigh = in.readArrayList(GeoPoint.class.getClassLoader()); + // boundingBox = in.readParcelable(BoundingBox.class.getClassLoader()); + // } +} diff --git a/vtm-app/src/org/osmdroid/routing/RouteLeg.java b/vtm-app/src/org/osmdroid/routing/RouteLeg.java new file mode 100644 index 00000000..d2f49b26 --- /dev/null +++ b/vtm-app/src/org/osmdroid/routing/RouteLeg.java @@ -0,0 +1,85 @@ +package org.osmdroid.routing; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +/** + * Road Leg is the portion of the route between 2 waypoints (intermediate points + * requested) + * + * @author M.Kergall + */ +public class RouteLeg implements Parcelable { + //final static Logger log = LoggerFactory.getLogger(RouteLeg.class); + + /** + * in km + */ + public double length; + /** + * in sec + */ + public double duration; + /** + * starting node of the leg, as index in nodes array + */ + public int startNodeIndex; + /** + * and ending node + */ + public int endNodeIndex; + + public RouteLeg() { + length = duration = 0.0; + startNodeIndex = endNodeIndex = 0; + } + + public RouteLeg(int startNodeIndex, int endNodeIndex, + List<RouteNode> nodes) { + this.startNodeIndex = startNodeIndex; + this.endNodeIndex = endNodeIndex; + length = duration = 0.0; + + for (int i = startNodeIndex; i <= endNodeIndex; i++) { + RouteNode node = nodes.get(i); + length += node.length; + duration += node.duration; + } + //log.debug("Leg: " + startNodeIndex + "-" + endNodeIndex + // + ", length=" + length + "km, duration=" + duration + "s"); + } + + //--- Parcelable implementation + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeDouble(length); + out.writeDouble(duration); + out.writeInt(startNodeIndex); + out.writeInt(endNodeIndex); + } + + public static final Parcelable.Creator<RouteLeg> CREATOR = new Parcelable.Creator<RouteLeg>() { + @Override + public RouteLeg createFromParcel(Parcel in) { + RouteLeg rl = new RouteLeg(); + rl.length = in.readDouble(); + rl.duration = in.readDouble(); + rl.startNodeIndex = in.readInt(); + rl.endNodeIndex = in.readInt(); + return rl; + } + + @Override + public RouteLeg[] newArray(int size) { + return new RouteLeg[size]; + } + }; +} diff --git a/vtm-app/src/org/osmdroid/routing/RouteNode.java b/vtm-app/src/org/osmdroid/routing/RouteNode.java new file mode 100644 index 00000000..a822418b --- /dev/null +++ b/vtm-app/src/org/osmdroid/routing/RouteNode.java @@ -0,0 +1,79 @@ +package org.osmdroid.routing; + +import org.oscim.core.GeoPoint; + +/** + * Route intersection, with instructions to continue. + * + * @author M.Kergall + */ +public class RouteNode { + /** + * @see <a + * href="http://open.mapquestapi.com/guidance/#maneuvertypes">Maneuver + * Types</a> + */ + public int maneuverType; + /** + * textual information on what to do at this intersection + */ + public String instructions; + /** + * index in route links array - internal use only, for MapQuest directions + */ + public int nextRouteLink; + /** + * in km to the next node + */ + public double length; + /** + * in seconds to the next node + */ + public double duration; + /** + * position of the node + */ + public GeoPoint location; + + public RouteNode() { + maneuverType = 0; + nextRouteLink = -1; + length = duration = 0.0; + } + + // --- Parcelable implementation + + // @Override + // public int describeContents() { + // return 0; + // } + // + // @Override + // public void writeToParcel(Parcel out, int flags) { + // out.writeInt(maneuverType); + // out.writeString(instructions); + // out.writeDouble(length); + // out.writeDouble(duration); + // out.writeParcelable(location, 0); + // } + // + // public static final Parcelable.Creator<RouteNode> CREATOR = new + // Parcelable.Creator<RouteNode>() { + // @Override + // public RouteNode createFromParcel(Parcel in) { + // RouteNode rn = new RouteNode(); + // rn.maneuverType = in.readInt(); + // rn.instructions = in.readString(); + // rn.length = in.readDouble(); + // rn.duration = in.readDouble(); + // rn.location = in.readParcelable(GeoPoint.class.getClassLoader()); + // return rn; + // } + // + // @Override + // public RouteNode[] newArray(int size) { + // return new RouteNode[size]; + // } + // }; + +} diff --git a/vtm-app/src/org/osmdroid/routing/RouteProvider.java b/vtm-app/src/org/osmdroid/routing/RouteProvider.java new file mode 100644 index 00000000..5a5b1fe6 --- /dev/null +++ b/vtm-app/src/org/osmdroid/routing/RouteProvider.java @@ -0,0 +1,67 @@ +package org.osmdroid.routing; + +import org.oscim.core.GeoPoint; +import org.oscim.layers.PathLayer; +import org.oscim.map.Map; +import org.osmdroid.routing.provider.GoogleRouteProvider; +import org.osmdroid.routing.provider.MapQuestRouteProvider; +import org.osmdroid.routing.provider.OSRMRouteProvider; + +import java.util.List; + +/** + * Generic class to get a route between a start and a destination point, going + * through a list of waypoints. + * + * @author M.Kergall + * @see MapQuestRouteProvider + * @see GoogleRouteProvider + * @see OSRMRouteProvider + */ +public abstract class RouteProvider { + + protected String mOptions; + + public abstract Route getRoute(List<GeoPoint> waypoints); + + public RouteProvider() { + mOptions = ""; + } + + /** + * Add an option that will be used in the route request. Note that some + * options are set in the request in all cases. + * + * @param requestOption see provider documentation. Just one example: + * "routeType=bicycle" for MapQuest; "mode=bicycling" for Google. + */ + public void addRequestOption(String requestOption) { + mOptions += "&" + requestOption; + } + + protected String geoPointAsString(GeoPoint p) { + StringBuffer result = new StringBuffer(); + double d = p.getLatitude(); + result.append(Double.toString(d)); + d = p.getLongitude(); + result.append("," + Double.toString(d)); + return result.toString(); + } + + /** + * Builds an overlay for the route shape with a default (and nice!) color. + * + * @return route shape overlay + */ + public static PathLayer buildRouteOverlay(Map map, Route route) { + int lineColor = 0x800000FF; + float lineWidth = 2.5f; + + PathLayer routeOverlay = new PathLayer(map, lineColor, lineWidth); + if (route != null) { + routeOverlay.setPoints(route.routeHigh); + } + return routeOverlay; + } + +} diff --git a/vtm-app/src/org/osmdroid/routing/provider/GoogleRouteProvider.java b/vtm-app/src/org/osmdroid/routing/provider/GoogleRouteProvider.java new file mode 100644 index 00000000..2870f20c --- /dev/null +++ b/vtm-app/src/org/osmdroid/routing/provider/GoogleRouteProvider.java @@ -0,0 +1,235 @@ +package org.osmdroid.routing.provider; + +import org.oscim.core.BoundingBox; +import org.oscim.core.GeoPoint; +import org.osmdroid.routing.Route; +import org.osmdroid.routing.RouteLeg; +import org.osmdroid.routing.RouteNode; +import org.osmdroid.routing.RouteProvider; +import org.osmdroid.utils.HttpConnection; +import org.osmdroid.utils.PolylineEncoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +/** + * class to get a route between a start and a destination point, going through a + * list of waypoints. <br> + * https://developers.google.com/maps/documentation/directions/<br> + * Note that displaying a route provided by Google on a non-Google map (like + * OSM) is not allowed by Google T&C. + * + * @author M.Kergall + */ +public class GoogleRouteProvider extends RouteProvider { + + final static Logger log = LoggerFactory.getLogger(GoogleRouteProvider.class); + + static final String GOOGLE_DIRECTIONS_SERVICE = "http://maps.googleapis.com/maps/api/directions/xml?"; + + /** + * Build the URL to Google Directions service returning a route in XML + * format + * + * @param waypoints ... + * @return ... + */ + protected String getUrl(List<GeoPoint> waypoints) { + StringBuffer urlString = new StringBuffer(GOOGLE_DIRECTIONS_SERVICE); + urlString.append("origin="); + GeoPoint p = waypoints.get(0); + urlString.append(geoPointAsString(p)); + urlString.append("&destination="); + int destinationIndex = waypoints.size() - 1; + p = waypoints.get(destinationIndex); + urlString.append(geoPointAsString(p)); + + for (int i = 1; i < destinationIndex; i++) { + if (i == 1) + urlString.append("&waypoints="); + else + urlString.append("%7C"); // the pipe (|), url-encoded + p = waypoints.get(i); + urlString.append(geoPointAsString(p)); + } + urlString.append("&units=metric&sensor=false"); + Locale locale = Locale.getDefault(); + urlString.append("&language=" + locale.getLanguage()); + urlString.append(mOptions); + return urlString.toString(); + } + + /** + * @param waypoints : list of GeoPoints. Must have at least 2 entries, start and + * end points. + * @return the route + */ + @Override + public Route getRoute(List<GeoPoint> waypoints) { + String url = getUrl(waypoints); + log.debug("GoogleRouteManager.getRoute:" + url); + Route route = null; + HttpConnection connection = new HttpConnection(); + connection.doGet(url); + InputStream stream = connection.getStream(); + if (stream != null) + route = getRouteXML(stream); + connection.close(); + if (route == null || route.routeHigh.size() == 0) { + //Create default route: + route = new Route(waypoints); + } else { + //finalize route data update: + for (RouteLeg leg : route.legs) { + route.duration += leg.duration; + route.length += leg.length; + } + route.status = Route.STATUS_OK; + } + log.debug("GoogleRouteManager.getRoute - finished"); + return route; + } + + protected Route getRouteXML(InputStream is) { + GoogleDirectionsHandler handler = new GoogleDirectionsHandler(); + try { + SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); + parser.parse(is, handler); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return handler.mRoute; + } + +} + +class GoogleDirectionsHandler extends DefaultHandler { + Route mRoute; + RouteLeg mLeg; + RouteNode mNode; + boolean isPolyline, isOverviewPolyline, isLeg, isStep, isDuration, isDistance, isBB; + int mValue; + double mLat, mLng; + double mNorth, mWest, mSouth, mEast; + private String mString; + + public GoogleDirectionsHandler() { + isOverviewPolyline = isBB = isPolyline = isLeg = isStep = isDuration = isDistance = false; + mRoute = new Route(); + } + + @Override + public void startElement(String uri, String localName, String name, + Attributes attributes) { + if (localName.equals("polyline")) { + isPolyline = true; + } else if (localName.equals("overview_polyline")) { + isOverviewPolyline = true; + } else if (localName.equals("leg")) { + mLeg = new RouteLeg(); + isLeg = true; + } else if (localName.equals("step")) { + mNode = new RouteNode(); + isStep = true; + } else if (localName.equals("duration")) { + isDuration = true; + } else if (localName.equals("distance")) { + isDistance = true; + } else if (localName.equals("bounds")) { + isBB = true; + } + mString = new String(); + } + + /** + * Overrides org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) + */ + public + @Override + void characters(char[] ch, int start, int length) { + String chars = new String(ch, start, length); + mString = mString.concat(chars); + } + + @Override + public void endElement(String uri, String localName, String name) { + if (localName.equals("points")) { + if (isPolyline) { + //detailed piece of route for the step, to add: + ArrayList<GeoPoint> polyLine = PolylineEncoder.decode(mString, 10); + mRoute.routeHigh.addAll(polyLine); + } else if (isOverviewPolyline) { + //low-def polyline for the whole route: + mRoute.setRouteLow(PolylineEncoder.decode(mString, 10)); + } + } else if (localName.equals("polyline")) { + isPolyline = false; + } else if (localName.equals("overview_polyline")) { + isOverviewPolyline = false; + } else if (localName.equals("value")) { + mValue = Integer.parseInt(mString); + } else if (localName.equals("duration")) { + if (isStep) + mNode.duration = mValue; + else + mLeg.duration = mValue; + isDuration = false; + } else if (localName.equals("distance")) { + if (isStep) + mNode.length = mValue / 1000.0; + else + mLeg.length = mValue / 1000.0; + isDistance = false; + } else if (localName.equals("html_instructions")) { + if (isStep) { + mString = mString.replaceAll("<[^>]*>", " "); //remove everything in <...> + mString = mString.replaceAll(" ", " "); + mNode.instructions = mString; + //log.debug(mString); + } + } else if (localName.equals("start_location")) { + if (isStep) + mNode.location = new GeoPoint(mLat, mLng); + } else if (localName.equals("step")) { + mRoute.nodes.add(mNode); + isStep = false; + } else if (localName.equals("leg")) { + mRoute.legs.add(mLeg); + isLeg = false; + } else if (localName.equals("lat")) { + mLat = Double.parseDouble(mString); + } else if (localName.equals("lng")) { + mLng = Double.parseDouble(mString); + } else if (localName.equals("northeast")) { + if (isBB) { + mNorth = mLat; + mEast = mLng; + } + } else if (localName.equals("southwest")) { + if (isBB) { + mSouth = mLat; + mWest = mLng; + } + } else if (localName.equals("bounds")) { + mRoute.boundingBox = new BoundingBox(mNorth, mEast, mSouth, mWest); + isBB = false; + } + } + +} diff --git a/vtm-app/src/org/osmdroid/routing/provider/MapQuestRouteProvider.java b/vtm-app/src/org/osmdroid/routing/provider/MapQuestRouteProvider.java new file mode 100644 index 00000000..4ff3ad6f --- /dev/null +++ b/vtm-app/src/org/osmdroid/routing/provider/MapQuestRouteProvider.java @@ -0,0 +1,289 @@ +package org.osmdroid.routing.provider; + +import org.oscim.core.BoundingBox; +import org.oscim.core.GeoPoint; +import org.osmdroid.routing.Route; +import org.osmdroid.routing.RouteNode; +import org.osmdroid.routing.RouteProvider; +import org.osmdroid.utils.HttpConnection; +import org.osmdroid.utils.PolylineEncoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +/** + * class to get a route between a start and a destination point, going through a + * list of waypoints. It uses MapQuest open, public and free API, based on + * OpenStreetMap data. <br> + * See http://open.mapquestapi.com/guidance + * + * @author M.Kergall + */ +public class MapQuestRouteProvider extends RouteProvider { + + final static Logger log = LoggerFactory.getLogger(MapQuestRouteProvider.class); + + static final String MAPQUEST_GUIDANCE_SERVICE = "http://open.mapquestapi.com/guidance/v0/route?"; + + /** + * Build the URL to MapQuest service returning a route in XML format + * + * @param waypoints : array of waypoints, as [lat, lng], from start point to end + * point. + * @return ... + */ + protected String getUrl(List<GeoPoint> waypoints) { + StringBuffer urlString = new StringBuffer(MAPQUEST_GUIDANCE_SERVICE); + urlString.append("from="); + GeoPoint p = waypoints.get(0); + urlString.append(geoPointAsString(p)); + + for (int i = 1; i < waypoints.size(); i++) { + p = waypoints.get(i); + urlString.append("&to=" + geoPointAsString(p)); + } + + urlString.append("&outFormat=xml"); + urlString.append("&shapeFormat=cmp"); // encoded polyline, much faster + + urlString.append("&narrativeType=text"); // or "none" + // Locale locale = Locale.getDefault(); + // urlString.append("&locale="+locale.getLanguage()+"_"+locale.getCountry()); + + urlString.append("&unit=k&fishbone=false"); + + // urlString.append("&generalizeAfter=500" /*+&generalize=2"*/); + // 500 points max, 2 meters tolerance + + // Warning: MapQuest Open API doc is sometimes WRONG: + // - use unit, not units + // - use fishbone, not enableFishbone + // - locale (fr_FR, en_US) is supported but not documented. + // - generalize and generalizeAfter are not properly implemented + urlString.append(mOptions); + return urlString.toString(); + } + + /** + * @param waypoints : list of GeoPoints. Must have at least 2 entries, start and + * end points. + * @return the route + */ + @Override + public Route getRoute(List<GeoPoint> waypoints) { + String url = getUrl(waypoints); + log.debug("MapQuestRouteManager.getRoute:" + url); + Route route = null; + HttpConnection connection = new HttpConnection(); + connection.doGet(url); + InputStream stream = connection.getStream(); + if (stream != null) + route = getRouteXML(stream, waypoints); + if (route == null || route.routeHigh.size() == 0) { + // Create default route: + route = new Route(waypoints); + } + connection.close(); + log.debug("MapQuestRouteManager.getRoute - finished"); + return route; + } + + /** + * XML implementation + * + * @param is : input stream to parse + * @param waypoints ... + * @return the route ... + */ + protected Route getRouteXML(InputStream is, List<GeoPoint> waypoints) { + XMLHandler handler = new XMLHandler(); + try { + SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); + parser.parse(is, handler); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + Route route = handler.mRoute; + if (route != null && route.routeHigh.size() > 0) { + route.nodes = finalizeNodes(route.nodes, handler.mLinks, route.routeHigh); + route.buildLegs(waypoints); + route.status = Route.STATUS_OK; + } + return route; + } + + protected List<RouteNode> finalizeNodes(List<RouteNode> mNodes, + List<RouteLink> mLinks, List<GeoPoint> polyline) { + int n = mNodes.size(); + if (n == 0) + return mNodes; + ArrayList<RouteNode> newNodes = new ArrayList<RouteNode>(n); + RouteNode lastNode = null; + for (int i = 1; i < n - 1; i++) { // 1, n-1 => first and last MapQuest + // nodes are irrelevant. + RouteNode node = mNodes.get(i); + RouteLink link = mLinks.get(node.nextRouteLink); + if (lastNode != null && (node.instructions == null || node.maneuverType == 0)) { + // this node is irrelevant, don't keep it, + // but update values of last node: + lastNode.length += link.mLength; + lastNode.duration += (node.duration + link.mDuration); + } else { + node.length = link.mLength; + node.duration += link.mDuration; + int locationIndex = link.mShapeIndex; + node.location = polyline.get(locationIndex); + newNodes.add(node); + lastNode = node; + } + } + // switch to the new array of nodes: + return newNodes; + } +} + +/** + * Route Link is a portion of route between 2 "nodes" or intersections + */ +class RouteLink { + /** + * in km/h + */ + public double mSpeed; + /** + * in km + */ + public double mLength; + /** + * in sec + */ + public double mDuration; + /** + * starting point of the link, as index in initial polyline + */ + public int mShapeIndex; +} + +/** + * XMLHandler: class to handle XML generated by MapQuest "guidance" open API. + */ +class XMLHandler extends DefaultHandler { + public Route mRoute; + public ArrayList<RouteLink> mLinks; + + boolean isBB; + boolean isGuidanceNodeCollection; + private String mString; + double mLat, mLng; + double mNorth, mWest, mSouth, mEast; + RouteLink mLink; + RouteNode mNode; + + public XMLHandler() { + isBB = isGuidanceNodeCollection = false; + mRoute = new Route(); + mLinks = new ArrayList<RouteLink>(); + } + + @Override + public void startElement(String uri, String localName, String name, + Attributes attributes) { + if (localName.equals("boundingBox")) + isBB = true; + else if (localName.equals("link")) + mLink = new RouteLink(); + else if (localName.equals("node")) + mNode = new RouteNode(); + else if (localName.equals("GuidanceNodeCollection")) + isGuidanceNodeCollection = true; + mString = new String(); + } + + /** + * Overrides org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) + */ + @Override + public void characters(char[] ch, int start, int length) { + String chars = new String(ch, start, length); + mString = mString.concat(chars); + } + + @Override + public void endElement(String uri, String localName, String name) { + if (localName.equals("lat")) { + mLat = Double.parseDouble(mString); + } else if (localName.equals("lng")) { + mLng = Double.parseDouble(mString); + } else if (localName.equals("shapePoints")) { + mRoute.routeHigh = PolylineEncoder.decode(mString, 10); + // log.debug("High="+mRoute.mRouteHigh.size()); + } else if (localName.equals("generalizedShape")) { + mRoute.setRouteLow(PolylineEncoder.decode(mString, 10)); + // log.debug("Low="+mRoute.mRouteLow.size()); + } else if (localName.equals("length")) { + mLink.mLength = Double.parseDouble(mString); + } else if (localName.equals("speed")) { + mLink.mSpeed = Double.parseDouble(mString); + } else if (localName.equals("shapeIndex")) { + mLink.mShapeIndex = Integer.parseInt(mString); + } else if (localName.equals("link")) { + // End of a link: update route attributes: + // GuidanceLinkCollection could in theory contain additional unused + // links, + // but normally not with fishbone set to false. + mLink.mDuration = mLink.mLength / mLink.mSpeed * 3600.0; + mLinks.add(mLink); + mRoute.length += mLink.mLength; + mRoute.duration += mLink.mDuration; + mLink = null; + } else if (localName.equals("turnCost")) { + int turnCost = Integer.parseInt(mString); + mNode.duration += turnCost; + mRoute.duration += turnCost; + } else if (localName.equals("maneuverType")) { + mNode.maneuverType = Integer.parseInt(mString); + } else if (localName.equals("info")) { + if (isGuidanceNodeCollection) { + if (mNode.instructions == null) + // this is first "info" value for this node, keep it: + mNode.instructions = mString; + } + } else if (localName.equals("linkId")) { + if (isGuidanceNodeCollection) + mNode.nextRouteLink = Integer.parseInt(mString); + } else if (localName.equals("node")) { + mRoute.nodes.add(mNode); + mNode = null; + } else if (localName.equals("GuidanceNodeCollection")) { + isGuidanceNodeCollection = false; + } else if (localName.equals("ul")) { + if (isBB) { + mNorth = mLat; + mWest = mLng; + } + } else if (localName.equals("lr")) { + if (isBB) { + mSouth = mLat; + mEast = mLng; + } + } else if (localName.equals("boundingBox")) { + mRoute.boundingBox = new BoundingBox(mNorth, mEast, mSouth, mWest); + isBB = false; + } + } +} diff --git a/vtm-app/src/org/osmdroid/routing/provider/OSRMRouteProvider.java b/vtm-app/src/org/osmdroid/routing/provider/OSRMRouteProvider.java new file mode 100644 index 00000000..976325b4 --- /dev/null +++ b/vtm-app/src/org/osmdroid/routing/provider/OSRMRouteProvider.java @@ -0,0 +1,294 @@ +package org.osmdroid.routing.provider; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.oscim.core.BoundingBox; +import org.oscim.core.GeoPoint; +import org.osmdroid.routing.Route; +import org.osmdroid.routing.RouteNode; +import org.osmdroid.routing.RouteProvider; +import org.osmdroid.utils.BonusPackHelper; +import org.osmdroid.utils.HttpConnection; +import org.osmdroid.utils.PolylineEncoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +/** + * get a route between a start and a destination point. It uses OSRM, a free + * open source routing service based on OpenSteetMap data. <br> + * See https://github.com/DennisOSRM/Project-OSRM/wiki/Server-api<br> + * It requests by default the OSRM demo site. Use setService() to request an + * other (for instance your own) OSRM service. <br> + * TODO: improve internationalization of instructions + * + * @author M.Kergall + */ +public class OSRMRouteProvider extends RouteProvider { + + final static Logger log = LoggerFactory.getLogger(OSRMRouteProvider.class); + + // 1 for 6 digit precision, 10 for 5 + private final static int ENCODING_PRECISION = 1; + + //static final String OSRM_SERVICE = "http://city.informatik.uni-bremen.de:5000/viaroute?"; + //static final String OSRM_SERVICE = "http://city.informatik.uni-bremen.de:5001/viaroute?"; + static final String OSRM_SERVICE = "http://router.project-osrm.org/viaroute?"; + + //Note that the result of OSRM is quite close to Cloudmade NavEngine format: + //http://developers.cloudmade.com/wiki/navengine/JSON_format + + protected String mServiceUrl; + protected String mUserAgent; + + /** + * mapping from OSRM directions to MapQuest maneuver IDs: + */ + static final HashMap<String, Integer> MANEUVERS; + + static { + MANEUVERS = new HashMap<String, Integer>(); + MANEUVERS.put("0", Integer.valueOf(0)); //No instruction + MANEUVERS.put("1", Integer.valueOf(1)); //Continue + MANEUVERS.put("2", Integer.valueOf(6)); //Slight right + MANEUVERS.put("3", Integer.valueOf(7)); //Right + MANEUVERS.put("4", Integer.valueOf(8)); //Sharp right + MANEUVERS.put("5", Integer.valueOf(12)); //U-turn + MANEUVERS.put("6", Integer.valueOf(5)); //Sharp left + MANEUVERS.put("7", Integer.valueOf(4)); //Left + MANEUVERS.put("8", Integer.valueOf(3)); //Slight left + MANEUVERS.put("9", Integer.valueOf(24)); //Arrived (at waypoint) + //MANEUVERS.put("10", Integer.valueOf(0)); //"Head" => used by OSRM as the start node + MANEUVERS.put("11-1", Integer.valueOf(27)); //Round-about, 1st exit + MANEUVERS.put("11-2", Integer.valueOf(28)); //2nd exit, etc ... + MANEUVERS.put("11-3", Integer.valueOf(29)); + MANEUVERS.put("11-4", Integer.valueOf(30)); + MANEUVERS.put("11-5", Integer.valueOf(31)); + MANEUVERS.put("11-6", Integer.valueOf(32)); + MANEUVERS.put("11-7", Integer.valueOf(33)); + MANEUVERS.put("11-8", Integer.valueOf(34)); //Round-about, 8th exit + MANEUVERS.put("15", Integer.valueOf(24)); //Arrived + } + + //From: Project-OSRM-Web / WebContent / localization / OSRM.Locale.en.js + // driving directions + // %s: route name + // %d: direction => removed + // <*>: will only be printed when there actually is a route name + static final HashMap<String, HashMap<String, String>> DIRECTIONS; + + static { + DIRECTIONS = new HashMap<String, HashMap<String, String>>(); + HashMap<String, String> directions; + + directions = new HashMap<String, String>(); + DIRECTIONS.put("en", directions); + directions.put("0", "Unknown instruction< on %s>"); + directions.put("1", "Continue< on %s>"); + directions.put("2", "Turn slight right< on %s>"); + directions.put("3", "Turn right< on %s>"); + directions.put("4", "Turn sharp right< on %s>"); + directions.put("5", "U-Turn< on %s>"); + directions.put("6", "Turn sharp left< on %s>"); + directions.put("7", "Turn left< on %s>"); + directions.put("8", "Turn slight left< on %s>"); + directions.put("9", "You have reached a waypoint of your trip"); + directions.put("10", "<Go on %s>"); + directions.put("11-1", "Enter roundabout and leave at first exit< on %s>"); + directions.put("11-2", "Enter roundabout and leave at second exit< on %s>"); + directions.put("11-3", "Enter roundabout and leave at third exit< on %s>"); + directions.put("11-4", "Enter roundabout and leave at fourth exit< on %s>"); + directions.put("11-5", "Enter roundabout and leave at fifth exit< on %s>"); + directions.put("11-6", "Enter roundabout and leave at sixth exit< on %s>"); + directions.put("11-7", "Enter roundabout and leave at seventh exit< on %s>"); + directions.put("11-8", "Enter roundabout and leave at eighth exit< on %s>"); + directions.put("11-9", "Enter roundabout and leave at nineth exit< on %s>"); + directions.put("15", "You have reached your destination"); + + directions = new HashMap<String, String>(); + DIRECTIONS.put("fr", directions); + directions.put("0", "Instruction inconnue< sur %s>"); + directions.put("1", "Continuez< sur %s>"); + directions.put("2", "Tournez légèrement à droite< sur %s>"); + directions.put("3", "Tournez à droite< sur %s>"); + directions.put("4", "Tournez fortement à droite< sur %s>"); + directions.put("5", "Faites demi-tour< sur %s>"); + directions.put("6", "Tournez fortement à gauche< sur %s>"); + directions.put("7", "Tournez à gauche< sur %s>"); + directions.put("8", "Tournez légèrement à gauche< sur %s>"); + directions.put("9", "Vous êtes arrivé à une étape de votre voyage"); + directions.put("10", "<Prenez %s>"); + directions.put("11-1", "Au rond-point, prenez la première sortie< sur %s>"); + directions.put("11-2", "Au rond-point, prenez la deuxième sortie< sur %s>"); + directions.put("11-3", "Au rond-point, prenez la troisième sortie< sur %s>"); + directions.put("11-4", "Au rond-point, prenez la quatrième sortie< sur %s>"); + directions.put("11-5", "Au rond-point, prenez la cinquième sortie< sur %s>"); + directions.put("11-6", "Au rond-point, prenez la sixième sortie< sur %s>"); + directions.put("11-7", "Au rond-point, prenez la septième sortie< sur %s>"); + directions.put("11-8", "Au rond-point, prenez la huitième sortie< sur %s>"); + directions.put("11-9", "Au rond-point, prenez la neuvième sortie< sur %s>"); + directions.put("15", "Vous êtes arrivé"); + + directions = new HashMap<String, String>(); + DIRECTIONS.put("pl", directions); + directions.put("0", "Nieznana instrukcja<w %s>"); + directions.put("1", "Kontynuuj jazdę<na %s>"); + directions.put("2", "Skręć lekko w prawo<w %s>"); + directions.put("3", "Skręć w prawo<w %s>"); + directions.put("4", "Skręć ostro w prawo<w %s>"); + directions.put("5", "Zawróć<na %s>"); + directions.put("6", "Skręć ostro w lewo<w %s>"); + directions.put("7", "Skręć w lewo<w %s>"); + directions.put("8", "Skręć lekko w lewo<w %s>"); + directions.put("9", "Dotarłeś do punktu pośredniego"); + directions.put("10", "<Jedź %s>"); + directions.put("11-1", "Wjedź na rondo i opuść je pierwszym zjazdem<w %s>"); + directions.put("11-2", "Wjedź na rondo i opuść je drugim zjazdem<w %s>"); + directions.put("11-3", "Wjedź na rondo i opuść je trzecim zjazdem<w %s>"); + directions.put("11-4", "Wjedź na rondo i opuść je czwartym zjazdem<w %s>"); + directions.put("11-5", "Wjedź na rondo i opuść je piątym zjazdem<w %s>"); + directions.put("11-6", "Wjedź na rondo i opuść je szóstym zjazdem<w %s>"); + directions.put("11-7", "Wjedź na rondo i opuść je siódmym zjazdem<w %s>"); + directions.put("11-8", "Wjedź na rondo i opuść je ósmym zjazdem<w %s>"); + directions.put("11-9", "Wjedź na rondo i opuść je dziewiątym zjazdem<w %s>"); + directions.put("15", "Dotarłeś do celu podróży"); + } + + public OSRMRouteProvider() { + super(); + mServiceUrl = OSRM_SERVICE; + mUserAgent = BonusPackHelper.DEFAULT_USER_AGENT; //set user agent to the default one. + } + + /** + * allows to request on an other site than OSRM demo site + * + * @param serviceUrl ... + */ + public void setService(String serviceUrl) { + mServiceUrl = serviceUrl; + } + + /** + * allows to send to OSRM service a user agent specific to the app, instead + * of the default user agent of OSMBonusPack lib. + * + * @param userAgent ... + */ + public void setUserAgent(String userAgent) { + mUserAgent = userAgent; + } + + protected String getUrl(List<GeoPoint> waypoints) { + StringBuffer urlString = new StringBuffer(mServiceUrl); + for (int i = 0; i < waypoints.size(); i++) { + GeoPoint p = waypoints.get(i); + urlString.append("&loc=" + geoPointAsString(p)); + } + urlString.append(mOptions); + return urlString.toString(); + } + + @Override + public Route getRoute(List<GeoPoint> waypoints) { + String url = getUrl(waypoints); + log.debug("OSRMRouteManager.getRoute:" + url); + + //String jString = BonusPackHelper.requestStringFromUrl(url); + HttpConnection connection = new HttpConnection(); + connection.setUserAgent(mUserAgent); + connection.doGet(url); + String jString = connection.getContentAsString(); + connection.close(); + + if (jString == null) { + log.error("OSRMRouteManager::getRoute: request failed."); + return new Route(waypoints); + } + Locale l = Locale.getDefault(); + HashMap<String, String> directions = DIRECTIONS.get(l.getLanguage()); + if (directions == null) + directions = DIRECTIONS.get("en"); + Route route = new Route(); + try { + JSONObject jObject = new JSONObject(jString); + String route_geometry = jObject.getString("route_geometry"); + route.routeHigh = PolylineEncoder.decode(route_geometry, ENCODING_PRECISION); + JSONArray jInstructions = jObject.getJSONArray("route_instructions"); + int n = jInstructions.length(); + RouteNode lastNode = null; + for (int i = 0; i < n; i++) { + JSONArray jInstruction = jInstructions.getJSONArray(i); + RouteNode node = new RouteNode(); + int positionIndex = jInstruction.getInt(3); + node.location = route.routeHigh.get(positionIndex); + node.length = jInstruction.getInt(2) / 1000.0; + node.duration = jInstruction.getInt(4); //Segment duration in seconds. + String direction = jInstruction.getString(0); + String routeName = jInstruction.getString(1); + if (lastNode != null && "1".equals(direction) && "".equals(routeName)) { + //node "Continue" with no route name is useless, don't add it + lastNode.length += node.length; + lastNode.duration += node.duration; + } else { + node.maneuverType = getManeuverCode(direction); + node.instructions = buildInstructions(direction, routeName, directions); + //log.debug(direction+"=>"+node.mManeuverType+"; "+node.mInstructions); + route.nodes.add(node); + lastNode = node; + } + } + JSONObject jSummary = jObject.getJSONObject("route_summary"); + route.length = jSummary.getInt("total_distance") / 1000.0; + route.duration = jSummary.getInt("total_time"); + } catch (JSONException e) { + e.printStackTrace(); + return new Route(waypoints); + } + if (route.routeHigh.size() == 0) { + //Create default route: + route = new Route(waypoints); + } else { + route.buildLegs(waypoints); + BoundingBox bb = BoundingBox.fromGeoPoints(route.routeHigh); + //Correcting osmdroid bug #359: + route.boundingBox = bb; + // new BoundingBox( + // bb.getLatSouthE6(), bb.getLonWestE6(), bb.getLatNorthE6(), bb.getLonEastE6()); + route.status = Route.STATUS_OK; + } + log.debug("OSRMRouteManager.getRoute - finished"); + return route; + } + + protected int getManeuverCode(String direction) { + Integer code = MANEUVERS.get(direction); + if (code != null) + return code.intValue(); + + return 0; + } + + protected String buildInstructions(String direction, String routeName, + HashMap<String, String> directions) { + if (directions == null) + return null; + direction = directions.get(direction); + if (direction == null) + return null; + String instructions = null; + if (routeName.equals("")) + //remove "<*>" + instructions = direction.replaceFirst("<[^>]*>", ""); + else { + direction = direction.replace('<', ' '); + direction = direction.replace('>', ' '); + instructions = String.format(direction, routeName); + } + return instructions; + } +} diff --git a/vtm-app/src/org/osmdroid/utils/BonusPackHelper.java b/vtm-app/src/org/osmdroid/utils/BonusPackHelper.java new file mode 100644 index 00000000..1e75e2f6 --- /dev/null +++ b/vtm-app/src/org/osmdroid/utils/BonusPackHelper.java @@ -0,0 +1,112 @@ +package org.osmdroid.utils; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Build; + +import java.io.FileNotFoundException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +/** + * Useful functions and common constants. + * + * @author M.Kergall + */ +public class BonusPackHelper { + + /** + * Log tag. + */ + public static final String LOG_TAG = "BONUSPACK"; + + /** + * User agent sent to services by default + */ + public static final String DEFAULT_USER_AGENT = "OsmBonusPack/1"; + + /** + * @return true if the device is the emulator, false if actual device. + */ + public static boolean isEmulator() { + //return Build.MANUFACTURER.equals("unknown"); + return ("google_sdk".equals(Build.PRODUCT) || "sdk".equals(Build.PRODUCT)); + } + + /** + * @param connection ... + * @return the whole content of the http request, as a string + */ + private static String readStream(HttpConnection connection) { + String result = connection.getContentAsString(); + return result; + } + + /** + * sends an http request, and returns the whole content result in a String. + * + * @param url ... + * @return the whole content, or null if any issue. + */ + public static String requestStringFromUrl(String url) { + HttpConnection connection = new HttpConnection(); + connection.doGet(url); + String result = readStream(connection); + connection.close(); + return result; + } + + /** + * Loads a bitmap from a url. + * + * @param url ... + * @return the bitmap, or null if any issue. + */ + public static Bitmap loadBitmap(String url) { + Bitmap bitmap = null; + try { + InputStream is = (InputStream) new URL(url).getContent(); + bitmap = BitmapFactory.decodeStream(new FlushedInputStream(is)); + //Alternative providing better handling on loading errors? + /* Drawable d = Drawable.createFromStream(new + * FlushedInputStream(is), null); if (is != null) is.close(); if (d + * != null) bitmap = ((BitmapDrawable)d).getBitmap(); */ + } catch (FileNotFoundException e) { + //log.debug("image not available: " + url); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + return bitmap; + } + + /** + * Workaround on Android issue see + * http://stackoverflow.com/questions/4601352 + * /createfromstream-in-android-returning-null-for-certain-url + */ + static class FlushedInputStream extends FilterInputStream { + public FlushedInputStream(InputStream inputStream) { + super(inputStream); + } + + @Override + public long skip(long n) throws IOException { + long totalBytesSkipped = 0L; + while (totalBytesSkipped < n) { + long bytesSkipped = in.skip(n - totalBytesSkipped); + if (bytesSkipped == 0L) { + int byteValue = read(); + if (byteValue < 0) + break; // we reached EOF + + bytesSkipped = 1; // we read one byte + } + totalBytesSkipped += bytesSkipped; + } + return totalBytesSkipped; + } + } +} diff --git a/vtm-app/src/org/osmdroid/utils/DouglasPeuckerReducer.java b/vtm-app/src/org/osmdroid/utils/DouglasPeuckerReducer.java new file mode 100644 index 00000000..a991a306 --- /dev/null +++ b/vtm-app/src/org/osmdroid/utils/DouglasPeuckerReducer.java @@ -0,0 +1,141 @@ +package org.osmdroid.utils; + +import org.oscim.core.GeoPoint; + +import java.util.ArrayList; +import java.util.List; + +/** + * Reduces the number of points in a shape using the Douglas-Peucker algorithm. <br> + * From: + * http://www.phpriot.com/articles/reducing-map-path-douglas-peucker-algorithm/4<br> + * Ported from PHP to Java. "marked" array added to optimize. + * + * @author M.Kergall + */ +public class DouglasPeuckerReducer { + + /** + * Reduce the number of points in a shape using the Douglas-Peucker + * algorithm + * + * @param shape The shape to reduce + * @param tolerance The tolerance to decide whether or not to keep a point, in the + * coordinate system of the points (micro-degrees here) + * @return the reduced shape + */ + public static List<GeoPoint> reduceWithTolerance(List<GeoPoint> shape, + double tolerance) { + int n = shape.size(); + // if a shape has 2 or less points it cannot be reduced + if (tolerance <= 0 || n < 3) { + return shape; + } + + boolean[] marked = new boolean[n]; //vertex indexes to keep will be marked as "true" + for (int i = 1; i < n - 1; i++) + marked[i] = false; + // automatically add the first and last point to the returned shape + marked[0] = marked[n - 1] = true; + + // the first and last points in the original shape are + // used as the entry point to the algorithm. + douglasPeuckerReduction( + shape, // original shape + marked, // reduced shape + tolerance, // tolerance + 0, // index of first point + n - 1 // index of last point + ); + + // all done, return the reduced shape + ArrayList<GeoPoint> newShape = new ArrayList<GeoPoint>(n); // the new shape to return + for (int i = 0; i < n; i++) { + if (marked[i]) + newShape.add(shape.get(i)); + } + return newShape; + } + + /** + * Reduce the points in shape between the specified first and last index. + * Mark the points to keep in marked[] + * + * @param shape The original shape + * @param marked The points to keep (marked as true) + * @param tolerance The tolerance to determine if a point is kept + * @param firstIdx The index in original shape's point of the starting point for + * this line segment + * @param lastIdx The index in original shape's point of the ending point for + * this line segment + */ + private static void douglasPeuckerReduction(List<GeoPoint> shape, boolean[] marked, + double tolerance, int firstIdx, int lastIdx) { + if (lastIdx <= firstIdx + 1) { + // overlapping indexes, just return + return; + } + + // loop over the points between the first and last points + // and find the point that is the farthest away + + double maxDistance = 0.0; + int indexFarthest = 0; + + GeoPoint firstPoint = shape.get(firstIdx); + GeoPoint lastPoint = shape.get(lastIdx); + + for (int idx = firstIdx + 1; idx < lastIdx; idx++) { + GeoPoint point = shape.get(idx); + + double distance = orthogonalDistance(point, firstPoint, lastPoint); + + // keep the point with the greatest distance + if (distance > maxDistance) { + maxDistance = distance; + indexFarthest = idx; + } + } + + if (maxDistance > tolerance) { + //The farthest point is outside the tolerance: it is marked and the algorithm continues. + marked[indexFarthest] = true; + + // reduce the shape between the starting point to newly found point + douglasPeuckerReduction(shape, marked, tolerance, firstIdx, indexFarthest); + + // reduce the shape between the newly found point and the finishing point + douglasPeuckerReduction(shape, marked, tolerance, indexFarthest, lastIdx); + } + //else: the farthest point is within the tolerance, the whole segment is discarded. + } + + /** + * Calculate the orthogonal distance from the line joining the lineStart and + * lineEnd points to point + * + * @param point The point the distance is being calculated for + * @param lineStart The point that starts the line + * @param lineEnd The point that ends the line + * @return The distance in points coordinate system + */ + public static double orthogonalDistance(GeoPoint point, GeoPoint lineStart, GeoPoint lineEnd) { + double area = Math.abs( + ( + 1.0 * lineStart.latitudeE6 * lineEnd.longitudeE6 + + 1.0 * lineEnd.latitudeE6 * point.longitudeE6 + + 1.0 * point.latitudeE6 * lineStart.longitudeE6 + - 1.0 * lineEnd.latitudeE6 * lineStart.longitudeE6 + - 1.0 * point.latitudeE6 * lineEnd.longitudeE6 + - 1.0 * lineStart.latitudeE6 * point.longitudeE6 + ) / 2.0 + ); + + double bottom = Math.hypot( + lineStart.latitudeE6 - lineEnd.latitudeE6, + lineStart.longitudeE6 - lineEnd.longitudeE6 + ); + + return (area / bottom * 2.0); + } +} diff --git a/vtm-app/src/org/osmdroid/utils/HttpConnection.java b/vtm-app/src/org/osmdroid/utils/HttpConnection.java new file mode 100644 index 00000000..89a9d33e --- /dev/null +++ b/vtm-app/src/org/osmdroid/utils/HttpConnection.java @@ -0,0 +1,119 @@ +package org.osmdroid.utils; + +import android.util.Log; + +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; + +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.TimeUnit; + +/** + * A "very very simple to use" class for performing http get and post requests. + * So many ways to do that, and potential subtle issues. + * If complexity should be added to handle even more issues, complexity should be put here and only here. + * <p/> + * Typical usage: + * <pre>HttpConnection connection = new HttpConnection(); + * connection.doGet("http://www.google.com"); + * InputStream stream = connection.getStream(); + * if (stream != null) { + * //use this stream, for buffer reading, or XML SAX parsing, or whatever... + * } + * connection.close();</pre> + */ +public class HttpConnection { + private final static int TIMEOUT_CONNECTION = 3000; //ms + private final static int TIMEOUT_SOCKET = 10000; //ms + + private static OkHttpClient client; + private InputStream stream; + private String mUserAgent; + private Response response; + + private static OkHttpClient getOkHttpClient() { + if (client == null) { + client = new OkHttpClient(); + client.setConnectTimeout(TIMEOUT_CONNECTION, TimeUnit.MILLISECONDS); + client.setReadTimeout(TIMEOUT_SOCKET, TimeUnit.MILLISECONDS); + } + return client; + } + + public HttpConnection() { + /* + client = new OkHttpClient(); + client.setConnectTimeout(TIMEOUT_CONNECTION, TimeUnit.MILLISECONDS); + client.setReadTimeout(TIMEOUT_SOCKET, TimeUnit.MILLISECONDS); + */ + } + + public void setUserAgent(String userAgent) { + mUserAgent = userAgent; + } + + public void doGet(final String url) { + try { + Request.Builder request = new Request.Builder().url(url); + if (mUserAgent != null) + request.addHeader("User-Agent", mUserAgent); + response = getOkHttpClient().newCall(request.build()).execute(); + Integer status = response.code(); + if (status != 200) { + Log.e(BonusPackHelper.LOG_TAG, "Invalid response from server: " + status.toString()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * @return the opened InputStream, or null if creation failed for any reason. + */ + public InputStream getStream() { + try { + if (response == null) + return null; + stream = response.body().byteStream(); + return stream; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * @return the whole content as a String, or null if creation failed for any reason. + */ + public String getContentAsString() { + try { + if (response == null) + return null; + return response.body().string(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Calling close once is mandatory. + */ + public void close() { + if (stream != null) { + try { + stream.close(); + stream = null; + } catch (IOException e) { + e.printStackTrace(); + } + } + /* + if (client != null) + client = null; + */ + } + +} diff --git a/vtm-app/src/org/osmdroid/utils/MathConstants.java b/vtm-app/src/org/osmdroid/utils/MathConstants.java new file mode 100644 index 00000000..bfb2792a --- /dev/null +++ b/vtm-app/src/org/osmdroid/utils/MathConstants.java @@ -0,0 +1,22 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.osmdroid.utils; + +public class MathConstants { + + public static final double PI180E6 = (Math.PI / 180) / 1000000.0; + public static final double PIx4 = Math.PI * 4; + +} diff --git a/vtm-app/src/org/osmdroid/utils/PolylineEncoder.java b/vtm-app/src/org/osmdroid/utils/PolylineEncoder.java new file mode 100644 index 00000000..ffef0484 --- /dev/null +++ b/vtm-app/src/org/osmdroid/utils/PolylineEncoder.java @@ -0,0 +1,94 @@ +package org.osmdroid.utils; + +import org.oscim.core.GeoPoint; + +import java.util.ArrayList; + +/** + * Methods to encode and decode a polyline with Google polyline + * encoding/decoding scheme. See + * https://developers.google.com/maps/documentation/utilities/polylinealgorithm + */ +public class PolylineEncoder { + + private static StringBuffer encodeSignedNumber(int num) { + int sgn_num = num << 1; + if (num < 0) { + sgn_num = ~(sgn_num); + } + return (encodeNumber(sgn_num)); + } + + private static StringBuffer encodeNumber(int num) { + StringBuffer encodeString = new StringBuffer(); + while (num >= 0x20) { + int nextValue = (0x20 | (num & 0x1f)) + 63; + encodeString.append((char) (nextValue)); + num >>= 5; + } + num += 63; + encodeString.append((char) (num)); + return encodeString; + } + + /** + * Encode a polyline with Google polyline encoding method + * + * @param polyline the polyline + * @param precision 1 for a 6 digits encoding, 10 for a 5 digits encoding. + * @return the encoded polyline, as a String + */ + public static String encode(ArrayList<GeoPoint> polyline, int precision) { + StringBuffer encodedPoints = new StringBuffer(); + int prev_lat = 0, prev_lng = 0; + for (GeoPoint trackpoint : polyline) { + int lat = trackpoint.latitudeE6 / precision; + int lng = trackpoint.longitudeE6 / precision; + encodedPoints.append(encodeSignedNumber(lat - prev_lat)); + encodedPoints.append(encodeSignedNumber(lng - prev_lng)); + prev_lat = lat; + prev_lng = lng; + } + return encodedPoints.toString(); + } + + /** + * Decode a "Google-encoded" polyline + * + * @param encodedString ... + * @param precision 1 for a 6 digits encoding, 10 for a 5 digits encoding. + * @return the polyline. + */ + public static ArrayList<GeoPoint> decode(String encodedString, int precision) { + ArrayList<GeoPoint> polyline = new ArrayList<GeoPoint>(); + int index = 0; + int len = encodedString.length(); + int lat = 0, lng = 0; + + while (index < len) { + int b, shift = 0, result = 0; + do { + b = encodedString.charAt(index++) - 63; + result |= (b & 0x1f) << shift; + shift += 5; + } while (b >= 0x20); + int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); + lat += dlat; + + shift = 0; + result = 0; + do { + b = encodedString.charAt(index++) - 63; + result |= (b & 0x1f) << shift; + shift += 5; + } while (b >= 0x20); + int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); + lng += dlng; + + GeoPoint p = new GeoPoint(lat * precision, lng * precision); + polyline.add(p); + } + + return polyline; + } +}