add osmdroid overlays + bonuspack

This commit is contained in:
Hannes Janetzek 2012-10-27 13:35:51 +02:00
parent 65a6f91f3c
commit ab5962d56c
114 changed files with 9562 additions and 1636 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,8 @@
<?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>

View File

@ -0,0 +1,8 @@
<?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>

View File

@ -8,7 +8,13 @@
<org.oscim.view.MapView
android:id="@+id/mapView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
android:layout_height="fill_parent" />
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<ToggleButton
android:id="@+id/snapToLocationView"

View File

@ -0,0 +1,53 @@
<?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_black" >
<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="#FFFFFF"
android:maxEms="17"
android:layout_gravity="left"
android:layout_weight="1"
android:text="Title" />
<Button android:id="@+id/bubble_moreinfo"
android:background="@drawable/btn_moreinfo"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_weight="0" />
</LinearLayout>
<TextView android:id="@+id/bubble_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
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="#FFFFFF"
android:textSize="10dp"
android:maxEms="17"
android:text="Address"
android:visibility="gone"
/>
</LinearLayout>
</LinearLayout>

View File

@ -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="centerCrop"
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>

46
res/layout/items_list.xml Normal file
View File

@ -0,0 +1,46 @@
<?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:orientation="vertical"
android:background="#FFFFFF"
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:ems="10"
android:completionThreshold="1" />
<Button
android:id="@+id/buttonSetPOITag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Search" />
</LinearLayout>
<ListView
android:id="@+id/items"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,49 @@
<?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_black" >
<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="#FFFFFF"
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="#FFFFFF"
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="#FFFFFF"
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>

View File

@ -0,0 +1,52 @@
<?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:visibility="gone" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
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="#000000"
android:maxEms="17"
android:layout_gravity="left"
android:layout_weight="1"
android:text="Title" />
<Button android:id="@+id/bubble_moreinfo"
android:background="@drawable/btn_moreinfo"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_weight="0" />
</LinearLayout>
<TextView android:id="@+id/bubble_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
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="#000000"
android:textSize="10dp"
android:maxEms="17"
android:text="Address"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,69 @@
<?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:orientation="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<EditText
android:id="@+id/editDestination"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_weight="1"
android:hint="@string/destination" />
<Button
android:id="@+id/buttonSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_weight="0"
android:text="Search" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<AutoCompleteTextView
android:id="@+id/poiTag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_weight="1"
android:completionThreshold="1"
android:hint="POI tag" />
<Button
android:id="@+id/buttonSetPOITag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_weight="0"
android:text="Search" />
</LinearLayout>
<TextView
android:id="@+id/routeInfo"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#FFBBBBBB"
android:textColor="#FF000000" />
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<org.osmdroid.views.MapView
android:id="@+id/map"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</RelativeLayout>
</LinearLayout>

15
res/menu/map_menu.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/menu_departure"
android:title="Set Departure"/>
<item
android:id="@+id/menu_destination"
android:title="Set Destination"/>
<item
android:id="@+id/menu_viapoint"
android:title="Add Via-Point"/>
<item
android:id="@+id/menu_none"
android:title="Cancel"/>
</menu>

View File

@ -13,9 +13,11 @@
<item
android:id="@+id/menu_position_my_location_disable"
android:title="@string/menu_position_my_location_disable"/>
<!-- <item
<!--
<item
android:id="@+id/menu_position_last_known"
android:title="@string/menu_position_last_known"/> -->
android:title="@string/menu_position_last_known"/>
-->
<item
android:id="@+id/menu_position_enter_coordinates"
android:title="@string/menu_position_enter_coordinates"/>
@ -28,57 +30,73 @@
<item
android:id="@+id/menu_rotation_disable"
android:title="@string/menu_rotation_disable"/>
<item
<item
android:id="@+id/menu_compass_enable"
android:title="@string/menu_compass_enable"/>
<item
<item
android:id="@+id/menu_compass_disable"
android:title="@string/menu_compass_disable"/>
</menu>
</item>
<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:showAsAction="never"
android:icon="@drawable/ic_menu_options"
android:title="@string/menu_preferences"/>
<menu>
<!-- android:icon="@drawable/ic_menu_preferences" -->
<item
android:id="@+id/menu_preferences"
android:showAsAction="never"
android:title="@string/menu_preferences"/>
<!-- android:icon="@drawable/ic_menu_mapmode" -->
<item
android:id="@+id/menu_render_theme"
android:showAsAction="never"
android:title="@string/menu_render_theme">
<menu>
<item
android:id="@+id/menu_render_theme_osmarender"
android:title="@string/menu_render_theme_osmarender"/>
<item
android:id="@+id/menu_render_theme_tronrender"
android:title="@string/menu_render_theme_tronrender"/>
<item
android:id="@+id/menu_render_theme_select_file"
android:title="@string/menu_render_theme_select_file"/>
</menu>
</item>
<!-- 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_about"
android:title="@string/menu_info_about"/>
<!-- 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_render_theme"
android:showAsAction="never"
android:title="@string/menu_render_theme">
<menu>
<item
android:id="@+id/menu_render_theme_osmarender"
android:title="@string/menu_render_theme_osmarender"/>
<item
android:id="@+id/menu_render_theme_tronrender"
android:title="@string/menu_render_theme_tronrender"/>
<item
android:id="@+id/menu_render_theme_select_file"
android:title="@string/menu_render_theme_select_file"/>
</menu>
</item>
<item
android:id="@+id/menu_info_about"
android:title="@string/menu_info_about"/>
<item
android:id="@+id/menu_pois"
android:title="@string/menu_pois"/>
<item
android:id="@+id/menu_poi_list"
android:title="@string/menu_poi_list"/>
</menu>

View File

@ -1,94 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<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_position_my_location_enable"
android:title="@string/menu_position_my_location_enable"/>
<item
android:id="@+id/menu_position_my_location_disable"
android:title="@string/menu_position_my_location_disable"/>
<!--
<item
android:id="@+id/menu_position_last_known"
android:title="@string/menu_position_last_known"/>
-->
<item
android:id="@+id/menu_position_enter_coordinates"
android:title="@string/menu_position_enter_coordinates"/>
<item
android:id="@+id/menu_position_map_center"
android:title="@string/menu_position_map_file_center"/>
<item
android:id="@+id/menu_rotation_enable"
android:title="@string/menu_rotation_enable"/>
<item
android:id="@+id/menu_rotation_disable"
android:title="@string/menu_rotation_disable"/>
<item
android:id="@+id/menu_compass_enable"
android:title="@string/menu_compass_enable"/>
<item
android:id="@+id/menu_compass_disable"
android:title="@string/menu_compass_disable"/>
</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:showAsAction="never"
android:icon="@drawable/ic_menu_options"
android:title="@string/menu_preferences"/>
<!-- 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_render_theme"
android:showAsAction="never"
android:title="@string/menu_render_theme">
<menu>
<item
android:id="@+id/menu_render_theme_osmarender"
android:title="@string/menu_render_theme_osmarender"/>
<item
android:id="@+id/menu_render_theme_tronrender"
android:title="@string/menu_render_theme_tronrender"/>
<item
android:id="@+id/menu_render_theme_select_file"
android:title="@string/menu_render_theme_select_file"/>
</menu>
</item>
<item
android:id="@+id/menu_info_about"
android:title="@string/menu_info_about"/>
</menu>

10
res/menu/poi_menu.xml Normal file
View File

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

101
res/values/poi_tags.xml Normal file
View File

@ -0,0 +1,101 @@
<?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>
</string-array>
</resources>

View File

@ -2,12 +2,13 @@
<resources>
<string-array name="preferences_map_generator_values">
<!-- <item>Mapfile</item> -->
<!-- <item>PostGIS</item> -->
<item>OpenScienceMap</item>
<item>OpenScienceMap2</item>
<item>OpenScienceMap2</item>
<item>Test</item>
</string-array>
</string-array>
<string-array name="preferences_scale_bar_unit_values">
<item>Imperial</item>
<item>Metric</item>
@ -111,5 +112,9 @@
<string name="unit_symbol_meter">&#160;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>

View File

@ -0,0 +1,38 @@
/*
* 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 org.oscim.view.MapView;
import android.app.Application;
import android.content.res.Resources;
// see http://androidcookbook.com : Android's Application Object as a "Singleton"
public class App extends Application {
public static final String TAG = "TileMap";
static MapView map;
static POISearch poiSearch;
static Resources res;
@Override
public void onCreate() {
super.onCreate();
res = getResources();
// Log.d("...", ">>>>>>>> INIT <<<<<<<");
// sInstance = this;
// sInstance.initializeInstance();
}
}

View File

@ -0,0 +1,113 @@
/*
* 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 org.oscim.core.GeoPoint;
import org.oscim.core.MapPosition;
import org.oscim.view.MapView;
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;
public class LocationDialog {
void prepareDialog(MapView mapView, final Dialog dialog) {
EditText editText = (EditText) dialog.findViewById(R.id.latitude);
GeoPoint mapCenter = mapView.getMapPosition().getMapCenter();
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); // FIXME
// map.getMapGenerator().getZoomLevelMax());
zoomlevel.setProgress(mapView.getMapPosition().getZoomLevel());
final TextView textView = (TextView) dialog.findViewById(R.id.zoomlevelValue);
textView.setText(String.valueOf(zoomlevel.getProgress()));
zoomlevel.setOnSeekBarChangeListener(new SeekBarChangeListener(textView));
}
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(true);
// 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);
byte zoom = (byte) (zoomLevelView.getProgress());
MapPosition mapPosition = new MapPosition(latitude,
longitude, zoom, 1, 0);
map.map.setMapCenter(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
}
}
}

View File

@ -107,14 +107,14 @@ public class LocationHandler {
}
if (bestLocation != null) {
byte zoom = mTileMap.mMapView.getMapPosition().getZoomLevel();
byte zoom = mTileMap.map.getMapPosition().getZoomLevel();
if (zoom < 12)
zoom = (byte) 12;
MapPosition mapPosition = new MapPosition(bestLocation.getLatitude(),
bestLocation.getLongitude(), zoom, 1, 0);
mTileMap.mMapView.setMapCenter(mapPosition);
mTileMap.map.setMapCenter(mapPosition);
} else {
mTileMap.showToastOnUiThread(mTileMap
@ -167,7 +167,7 @@ public class LocationHandler {
mSnapToLocation = false;
mSnapToLocationView.setChecked(false);
mTileMap.mMapView.setClickable(true);
mTileMap.map.setClickable(true);
if (showToast) {
mTileMap.showToastOnUiThread(mTileMap
@ -186,7 +186,7 @@ public class LocationHandler {
if (!mSnapToLocation) {
mSnapToLocation = true;
mTileMap.mMapView.setClickable(false);
mTileMap.map.setClickable(false);
if (showToast) {
mTileMap.showToastOnUiThread(mTileMap
@ -228,7 +228,7 @@ public class LocationHandler {
if (mSetCenter || isSnapToLocationEnabled()) {
mSetCenter = false;
mTileMap.mMapView.setCenter(point);
mTileMap.map.setCenter(point);
}
}

View File

@ -0,0 +1,96 @@
/*
* 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;
public class MapInfoDialog {
//onCreate
// else if (id == DIALOG_INFO_MAP_FILE) {
// builder.setIcon(android.R.drawable.ic_menu_info_details);
// builder.setTitle(R.string.menu_info_map_file);
// LayoutInflater factory = LayoutInflater.from(this);
// builder.setView(factory.inflate(R.layout.dialog_info_map_file, null));
// builder.setPositiveButton(R.string.ok, null);
// return builder.create();
//onPrepare
// } else if (id == DIALOG_INFO_MAP_FILE) {
// MapInfo mapInfo = map.getMapDatabase().getMapInfo();
//
// TextView textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewName);
// textView.setText(map.getMapFile());
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewSize);
// textView.setText(FileUtils.formatFileSize(mapInfo.fileSize,
// getResources()));
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewVersion);
// textView.setText(String.valueOf(mapInfo.fileVersion));
//
// // textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewDebug);
// // if (mapFileInfo.debugFile) {
// // textView.setText(R.string.info_map_file_debug_yes);
// // } else {
// // textView.setText(R.string.info_map_file_debug_no);
// // }
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewDate);
// Date date = new Date(mapInfo.mapDate);
// textView.setText(DateFormat.getDateTimeInstance().format(date));
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewArea);
// BoundingBox boundingBox = mapInfo.boundingBox;
// textView.setText(boundingBox.getMinLatitude() + ", "
// + boundingBox.getMinLongitude() + " - \n"
// + boundingBox.getMaxLatitude() + ", " +
// boundingBox.getMaxLongitude());
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewStartPosition);
// GeoPoint startPosition = mapInfo.startPosition;
// if (startPosition == null) {
// textView.setText(null);
// } else {
// textView.setText(startPosition.getLatitude() + ", "
// + startPosition.getLongitude());
// }
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewStartZoomLevel);
// Byte startZoomLevel = mapInfo.startZoomLevel;
// if (startZoomLevel == null) {
// textView.setText(null);
// } else {
// textView.setText(startZoomLevel.toString());
// }
//
// textView = (TextView) dialog
// .findViewById(R.id.infoMapFileViewLanguagePreference);
// textView.setText(mapInfo.languagePreference);
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewComment);
// textView.setText(mapInfo.comment);
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewCreatedBy);
// textView.setText(mapInfo.createdBy);
}

View File

@ -0,0 +1,261 @@
/*
* 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 java.util.ArrayList;
import org.osmdroid.location.POI;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
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.View.OnCreateContextMenuListener;
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;
/**
* Activity showing POIs as a list.
* @author M.Kergall
*/
public class POIActivity extends Activity {
AutoCompleteTextView poiTagText;
POIAdapter mAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.items_list);
// TextView title = (TextView) findViewById(R.id.title);
// title.setText("Points of Interest");
ListView list = (ListView) findViewById(R.id.items);
Intent myIntent = getIntent();
final ArrayList<POI> pois = myIntent.getParcelableArrayListExtra("POI");
final int currentNodeId = myIntent.getIntExtra("ID", -1);
POIAdapter adapter = new POIAdapter(this, pois);
mAdapter = adapter;
// Log.d(App.TAG, "got POIs:" + (pois == null ? pois : pois.size()));
list.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View view, int position, long index) {
Intent intent = new Intent();
intent.putExtra("ID", position);
setResult(RESULT_OK, intent);
finish();
}
});
list.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
// TODO Auto-generated method stub
Log.d(App.TAG, "context menu created 2");
}
});
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) {
//Hide the soft keyboard:
InputMethodManager imm = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(poiTagText.getWindowToken(), 0);
//Start search:
App.poiSearch.getPOIAsync(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);
}
}
@Override
protected void onNewIntent(Intent intent) {
Log.d(App.TAG, "NEW 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();
}
// http://www.mikeplate.com/2010/01/21/show-a-context-menu-for-long-clicks-in-an-android-listview/
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
if (v.getId() == R.id.items) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
Log.d(App.TAG, "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.d(App.TAG, "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) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(poi.url));
startActivity(i);
}
return true;
}
return super.onContextItemSelected(item);
}
}
class POIAdapter extends BaseAdapter implements OnClickListener {
private Context mContext;
private ArrayList<POI> mPois;
public POIAdapter(Context context, ArrayList<POI> pois) {
mContext = context;
mPois = pois;
}
public void setPOI(ArrayList<POI> pois) {
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 convertView, ViewGroup viewGroup) {
POI entry = (POI) getItem(position);
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.item_layout, null);
} else {
}
TextView tvTitle = (TextView) convertView.findViewById(R.id.title);
tvTitle.setText((entry.url == null ? "" : "[link] ") + entry.type);
TextView tvDetails = (TextView) convertView.findViewById(R.id.details);
tvDetails.setText(entry.description);
ImageView iv = (ImageView) convertView.findViewById(R.id.thumbnail);
//ivManeuver.setImageBitmap(entry.mThumbnail);
// iv.getT
// entry.fetchThumbnailOnThread(iv);
entry.fetchThumbnail(iv);
return convertView;
}
@Override
public void onClick(View arg0) {
Log.d(App.TAG, "click" + arg0.getId());
//nothing to do.
}
}

View File

@ -0,0 +1,197 @@
/*
* 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 java.util.ArrayList;
import org.oscim.core.BoundingBox;
import org.oscim.overlay.OverlayItem;
import org.osmdroid.location.FlickrPOIProvider;
import org.osmdroid.location.GeoNamesPOIProvider;
import org.osmdroid.location.NominatimPOIProvider;
import org.osmdroid.location.POI;
import org.osmdroid.location.PicasaPOIProvider;
import org.osmdroid.overlays.ExtendedOverlayItem;
import org.osmdroid.overlays.ItemizedOverlayWithBubble;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.AutoCompleteTextView;
import android.widget.Toast;
public class POISearch {
ArrayList<POI> mPOIs;
ItemizedOverlayWithBubble<ExtendedOverlayItem> poiMarkers;
AutoCompleteTextView poiTagText;
final TileMap tileMap;
POISearch(TileMap tileMap) {
this.tileMap = tileMap;
//POI markers:
final ArrayList<ExtendedOverlayItem> poiItems = new ArrayList<ExtendedOverlayItem>();
poiMarkers = new ItemizedOverlayWithBubble<ExtendedOverlayItem>(tileMap.map, tileMap,
poiItems, null); //new POIInfoWindow(map));
tileMap.map.getOverlays().add(poiMarkers);
// if (savedInstanceState != null) {
// mPOIs = savedInstanceState.getParcelableArrayList("poi");
// updateUIWithPOI(mPOIs);
// }
}
// void init() {
//
// }
class POITask extends AsyncTask<Object, Void, ArrayList<POI>> {
String mTag;
@Override
protected ArrayList<POI> doInBackground(Object... params) {
mTag = (String) params[0];
if (mTag == null || mTag.equals("")) {
return null;
} else if (mTag.equals("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:
BoundingBox bb = App.map.getBoundingBox();
ArrayList<POI> pois = poiProvider.getPOIInside(bb, 30);
return pois;
} else if (mTag.equals("flickr")) {
FlickrPOIProvider poiProvider = new FlickrPOIProvider(
"c39be46304a6c6efda8bc066c185cd7e");
BoundingBox bb = App.map.getBoundingBox();
ArrayList<POI> pois = poiProvider.getPOIInside(bb, 20);
return pois;
} else if (mTag.startsWith("picasa")) {
PicasaPOIProvider poiProvider = new PicasaPOIProvider(null);
BoundingBox bb = App.map.getBoundingBox();
String q = mTag.substring("picasa".length());
ArrayList<POI> pois = poiProvider.getPOIInside(bb, 20, q);
return pois;
}
else {
NominatimPOIProvider poiProvider = new NominatimPOIProvider();
// poiProvider.setService(NominatimPOIProvider.MAPQUEST_POI_SERVICE);
poiProvider.setService(NominatimPOIProvider.NOMINATIM_POI_SERVICE);
ArrayList<POI> pois;
// if (destinationPoint == null) {
BoundingBox bb = App.map.getBoundingBox();
pois = poiProvider.getPOIInside(bb, mTag, 100);
// } else {
// pois = poiProvider.getPOIAlong(mRoad.getRouteLow(), mTag, 100, 2.0);
// }
return pois;
}
}
@Override
protected void onPostExecute(ArrayList<POI> pois) {
mPOIs = pois;
if (mTag.equals("")) {
//no search, no message
} else if (mPOIs == null) {
Toast.makeText(tileMap.getApplicationContext(),
"Technical issue when getting " + mTag + " POI.", Toast.LENGTH_LONG)
.show();
} else {
Toast.makeText(tileMap.getApplicationContext(),
"" + mPOIs.size() + " " + mTag + " entries found",
Toast.LENGTH_LONG).show();
// if (mTag.equals("flickr") || mTag.startsWith("picasa") || mTag.equals("wikipedia"))
// startAsyncThumbnailsLoading(mPOIs);
}
updateUIWithPOI(mPOIs);
}
}
void updateUIWithPOI(ArrayList<POI> pois) {
if (pois != null) {
for (POI poi : pois) {
int end = 0, first = 0;
String desc = null;
String name = poi.description;
if (poi.serviceId == POI.POI_SERVICE_NOMINATIM) {
if (name != null) {
// FIXME or nominatim...
// String name = "";
for (int i = 0; i < 3; i++) {
int pos = poi.description.indexOf(',', end);
if (pos > 0) {
if (i == 0) {
name = poi.description.substring(0, pos);
first = pos + 2;
}
end = pos + 1;
}
}
if (end > 0)
desc = poi.description.substring(first, end - 1);
else
desc = poi.description;
}
}
ExtendedOverlayItem poiMarker = new ExtendedOverlayItem(
poi.type + (name == null ? "" : ": " + name), desc, poi.location);
Drawable marker = null;
if (poi.serviceId == POI.POI_SERVICE_NOMINATIM) {
marker = App.res.getDrawable(R.drawable.marker_poi_default);
} else if (poi.serviceId == POI.POI_SERVICE_GEONAMES_WIKIPEDIA) {
if (poi.rank < 90)
marker = App.res.getDrawable(R.drawable.marker_poi_wikipedia_16);
else
marker = App.res.getDrawable(R.drawable.marker_poi_wikipedia_32);
} else if (poi.serviceId == POI.POI_SERVICE_FLICKR) {
marker = App.res.getDrawable(R.drawable.marker_poi_flickr);
} else if (poi.serviceId == POI.POI_SERVICE_PICASA) {
marker = App.res.getDrawable(R.drawable.marker_poi_picasa_24);
poiMarker.setSubDescription(poi.category);
}
poiMarker.setMarker(marker);
poiMarker.setMarkerHotspot(OverlayItem.HotspotPlace.CENTER);
//thumbnail loading moved in POIInfoWindow.onOpen for better performances.
poiMarker.setRelatedObject(poi);
poiMarkers.addItem(poiMarker);
}
Log.d(App.TAG, "SEND INTENT");
Intent intent = new Intent(tileMap.getApplicationContext(), POIActivity.class);
intent.putParcelableArrayListExtra("POI", mPOIs);
intent.putExtra("ID", poiMarkers.getBubbledItemId());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
tileMap.startActivityForResult(intent, TileMap.POIS_REQUEST);
}
App.map.redrawMap();
}
void getPOIAsync(String tag) {
poiMarkers.removeAllItems();
new POITask().execute(tag);
}
}

View File

@ -0,0 +1,403 @@
/*
* 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 java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.oscim.core.GeoPoint;
import org.oscim.overlay.Overlay;
import org.oscim.overlay.OverlayItem;
import org.oscim.overlay.PathOverlay;
import org.oscim.view.MapView;
import org.osmdroid.location.GeocoderNominatim;
import org.osmdroid.overlays.DefaultInfoWindow;
import org.osmdroid.overlays.ExtendedOverlayItem;
import org.osmdroid.overlays.ItemizedOverlayWithBubble;
import org.osmdroid.overlays.MapEventsOverlay;
import org.osmdroid.overlays.MapEventsReceiver;
import org.osmdroid.routing.OSRMRoadManager;
import org.osmdroid.routing.Road;
import org.osmdroid.routing.RoadManager;
import org.osmdroid.routing.RoadNode;
import android.graphics.drawable.Drawable;
import android.location.Address;
import android.os.AsyncTask;
import android.util.Log;
import android.view.MenuItem;
import android.widget.Toast;
public class RouteSearch implements MapEventsReceiver {
protected Road mRoad;
protected PathOverlay mRoadOverlay;
protected ItemizedOverlayWithBubble<ExtendedOverlayItem> mRoadNodeMarkers;
protected GeoPoint startPoint, destinationPoint;
protected ArrayList<GeoPoint> viaPoints;
protected static int START_INDEX = -2, DEST_INDEX = -1;
protected ExtendedOverlayItem markerStart, markerDestination;
protected ItemizedOverlayWithBubble<ExtendedOverlayItem> itineraryMarkers;
private final TileMap tileMap;
RouteSearch(TileMap tileMap) {
// keep context and mapview reference
this.tileMap = tileMap;
//To use MapEventsReceiver methods, we add a MapEventsOverlay:
MapEventsOverlay overlay = new MapEventsOverlay(tileMap, this);
tileMap.map.getOverlays().add(overlay);
// if (savedInstanceState == null) {
// Location l = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
// if (l != null) {
// startPoint = new GeoPoint(l.getLatitude(), l.getLongitude());
// } else {
// //we put a hard-coded start
startPoint = new GeoPoint(53.067221, 8.78767);
// }
destinationPoint = null;
viaPoints = new ArrayList<GeoPoint>();
// mapController.setZoom(9);
// mapController.setCenter(startPoint);
// }
// Itinerary markers:
final ArrayList<ExtendedOverlayItem> waypointsItems = new ArrayList<ExtendedOverlayItem>();
itineraryMarkers = new ItemizedOverlayWithBubble<ExtendedOverlayItem>(tileMap.map, tileMap,
waypointsItems, new ViaPointInfoWindow(R.layout.itinerary_bubble, tileMap.map));
tileMap.map.getOverlays().add(itineraryMarkers);
updateUIWithItineraryMarkers();
//Route and Directions
final ArrayList<ExtendedOverlayItem> roadItems = new ArrayList<ExtendedOverlayItem>();
mRoadNodeMarkers = new ItemizedOverlayWithBubble<ExtendedOverlayItem>(tileMap, roadItems,
tileMap.map);
tileMap.map.getOverlays().add(mRoadNodeMarkers);
// if (savedInstanceState != null) {
// mRoad = savedInstanceState.getParcelable("road");
// updateUIWithRoad(mRoad);
// }
}
//------------- Geocoding and Reverse Geocoding
/**
* Reverse Geocoding
* @param p
* ...
* @return ...
*/
public String getAddress(GeoPoint p) {
GeocoderNominatim geocoder = new GeocoderNominatim(tileMap);
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> {
ExtendedOverlayItem marker;
@Override
protected String doInBackground(Object... params) {
marker = (ExtendedOverlayItem) params[0];
return getAddress(marker.getPoint());
}
@Override
protected void onPostExecute(String result) {
marker.setDescription(result);
//itineraryMarkers.showBubbleOnItem(???, map); //open bubble on the item
}
}
//------------ Itinerary markers
/* add (or replace) an item in markerOverlays. p position. */
public ExtendedOverlayItem putMarkerItem(ExtendedOverlayItem item, GeoPoint p, int index,
int titleResId, int markerResId, int iconResId) {
if (item != null) {
itineraryMarkers.removeItem(item);
}
Drawable marker = App.res.getDrawable(markerResId);
String title = App.res.getString(titleResId);
ExtendedOverlayItem overlayItem = new ExtendedOverlayItem(title, "", p);
overlayItem.setMarkerHotspot(OverlayItem.HotspotPlace.BOTTOM_CENTER);
overlayItem.setMarker(marker);
if (iconResId != -1)
overlayItem.setImage(App.res.getDrawable(iconResId));
overlayItem.setRelatedObject(Integer.valueOf(index));
itineraryMarkers.addItem(overlayItem);
tileMap.map.redrawMap();
//map.invalidate();
//Start geocoding task to update the description of the marker with its address:
new GeocodingTask().execute(overlayItem);
return overlayItem;
}
public void addViaPoint(GeoPoint p) {
viaPoints.add(p);
putMarkerItem(null, p, viaPoints.size() - 1,
R.string.viapoint, R.drawable.marker_via, -1);
}
public void removePoint(int index) {
if (index == START_INDEX)
startPoint = null;
else if (index == DEST_INDEX)
destinationPoint = null;
else
viaPoints.remove(index);
getRoadAsync();
updateUIWithItineraryMarkers();
}
public void updateUIWithItineraryMarkers() {
itineraryMarkers.removeAllItems();
//Start marker:
if (startPoint != null) {
markerStart = putMarkerItem(null, startPoint, START_INDEX,
R.string.departure, R.drawable.marker_departure, -1);
}
//Via-points markers if any:
for (int index = 0; index < viaPoints.size(); index++) {
putMarkerItem(null, viaPoints.get(index), index,
R.string.viapoint, R.drawable.marker_via, -1);
}
//Destination marker if any:
if (destinationPoint != null) {
markerDestination = putMarkerItem(null, destinationPoint, DEST_INDEX,
R.string.destination,
R.drawable.marker_destination, -1);
}
}
//------------ Route and Directions
private void putRoadNodes(Road road) {
mRoadNodeMarkers.removeAllItems();
Drawable marker = App.res.getDrawable(R.drawable.marker_node);
int n = road.nodes.size();
// TypedArray iconIds = App.res.obtainTypedArray(R.array.direction_icons);
for (int i = 0; i < n; i++) {
RoadNode node = road.nodes.get(i);
String instructions = (node.instructions == null ? "" : node.instructions);
ExtendedOverlayItem nodeMarker = new ExtendedOverlayItem(
"Step " + (i + 1), instructions, node.location);
nodeMarker.setSubDescription(road.getLengthDurationText(node.length, node.duration));
nodeMarker.setMarkerHotspot(OverlayItem.HotspotPlace.CENTER);
nodeMarker.setMarker(marker);
// int iconId = iconIds.getResourceId(node.mManeuverType, R.drawable.ic_empty);
// if (iconId != R.drawable.ic_empty) {
// Drawable icon = App.res.getDrawable(iconId);
// nodeMarker.setImage(icon);
// }
mRoadNodeMarkers.addItem(nodeMarker);
}
}
void updateUIWithRoad(Road road) {
mRoadNodeMarkers.removeAllItems();
List<Overlay> mapOverlays = tileMap.map.getOverlays();
if (mRoadOverlay != null) {
mapOverlays.remove(mRoadOverlay);
}
if (road == null)
return;
if (road.status == Road.STATUS_DEFAULT)
Toast.makeText(tileMap, "We have a problem to get the route",
Toast.LENGTH_SHORT).show();
mRoadOverlay = RoadManager.buildRoadOverlay(tileMap.map, road, tileMap);
Overlay removedOverlay = mapOverlays.set(1, mRoadOverlay);
//we set the road overlay at the "bottom", just above the MapEventsOverlay,
//to avoid covering the other overlays.
mapOverlays.add(removedOverlay);
putRoadNodes(road);
tileMap.map.redrawMap();
// map.invalidate();
//Set route info in the text view:
// ((TextView) findViewById(R.id.routeInfo)).setText(road.getLengthDurationText(-1));
}
/**
* Async task to get the road in a separate thread.
*/
class UpdateRoadTask extends AsyncTask<WayPoints, Void, Road> {
@Override
protected Road doInBackground(WayPoints... wp) {
ArrayList<GeoPoint> waypoints = wp[0].waypoints;
//RoadManager roadManager = new GoogleRoadManager();
RoadManager roadManager = new OSRMRoadManager();
roadManager.addRequestOption("");
/* RoadManager roadManager = new MapQuestRoadManager(); Locale
* locale = Locale.getDefault();
* roadManager.addRequestOption("locale="+locale.getLanguage
* ()+"_"+locale.getCountry()); */
return roadManager.getRoad(waypoints);
}
@Override
protected void onPostExecute(Road result) {
mRoad = result;
updateUIWithRoad(result);
/// ??? getPOIAsync(poiTagText.getText().toString());
}
}
// Just to make JAVA shut up!
class WayPoints {
public ArrayList<GeoPoint> waypoints;
}
public void getRoadAsync() {
mRoad = null;
if (startPoint == null || destinationPoint == null) {
updateUIWithRoad(mRoad);
return;
}
ArrayList<GeoPoint> waypoints = new ArrayList<GeoPoint>(2);
waypoints.add(startPoint);
//add intermediate via points:
for (GeoPoint p : viaPoints) {
waypoints.add(p);
}
waypoints.add(destinationPoint);
WayPoints wp = new WayPoints();
wp.waypoints = waypoints;
new UpdateRoadTask().execute(wp);
}
//------------ MapEventsReceiver implementation
GeoPoint tempClickedGeoPoint; //any other way to pass the position to the menu ???
@Override
public boolean longPressHelper(GeoPoint p) {
Log.d(TileMap.TAG, ">>> got long press event " + p);
tempClickedGeoPoint = p;
//new GeoPoint((GeoPoint) p);
//menu is hooked on the "Search" button
// Button searchButton = (Button) findViewById(R.id.buttonSearch);
// tileMap.openContextMenu(searchButton);
tileMap.openContextMenu(tileMap.map);
return true;
}
@Override
public boolean singleTapUpHelper(GeoPoint p) {
Log.d(App.TAG, "single tap up");
mRoadNodeMarkers.hideBubble();
itineraryMarkers.hideBubble();
return false;
}
// ----------- context menu
boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_departure:
startPoint = tempClickedGeoPoint;
//new GeoPoint((GeoPoint) tempClickedGeoPoint);
markerStart = putMarkerItem(markerStart, startPoint, START_INDEX,
R.string.departure, R.drawable.marker_departure, -1);
getRoadAsync();
return true;
case R.id.menu_destination:
destinationPoint = tempClickedGeoPoint;
//new GeoPoint((GeoPoint) tempClickedGeoPoint);
markerDestination = putMarkerItem(markerDestination, destinationPoint, DEST_INDEX,
R.string.destination,
R.drawable.marker_destination, -1);
getRoadAsync();
return true;
case R.id.menu_viapoint:
GeoPoint viaPoint = tempClickedGeoPoint; //new GeoPoint((GeoPoint) tempClickedGeoPoint);
addViaPoint(viaPoint);
getRoadAsync();
return true;
default:
}
return false;
}
class ViaPointInfoWindow extends DefaultInfoWindow {
int mSelectedPoint;
public ViaPointInfoWindow(int layoutResId, MapView mapView) {
super(layoutResId, mapView);
// Button btnDelete = (Button)(mView.findViewById(R.id.bubble_delete));
// btnDelete.setOnClickListener(new View.OnClickListener() {
// public void onClick(View view) {
// //Call the removePoint method on MapActivity.
// //TODO: find a cleaner way to do that!
// MapActivity mapActivity = (MapActivity)view.getContext();
// mapActivity.removePoint(mSelectedPoint);
// close();
// }
// });
}
@Override
public void onOpen(ExtendedOverlayItem item) {
mSelectedPoint = ((Integer) item.getRelatedObject()).intValue();
super.onOpen(item);
}
}
}

View File

@ -1,3 +1,19 @@
/* Copyright 2010, 2011, 2012 mapsforge.org
* Copyright 2012 Hannes Janetzek
* Copyright 2012 osmdroidbonuspack: M.Kergall
*
* 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 java.io.FileFilter;
@ -7,8 +23,6 @@ import org.oscim.app.filefilter.FilterByFileExtension;
import org.oscim.app.filefilter.ValidRenderTheme;
import org.oscim.app.filepicker.FilePicker;
import org.oscim.app.preferences.EditPreferences;
import org.oscim.core.GeoPoint;
import org.oscim.core.MapPosition;
import org.oscim.database.MapDatabases;
import org.oscim.theme.InternalRenderTheme;
import org.oscim.utils.AndroidUtils;
@ -19,7 +33,6 @@ import org.oscim.view.MapView;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
@ -29,57 +42,96 @@ import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import android.widget.Toast;
/**
* A map application which uses the features from the mapsforge map library. The
* map can be centered to the current location. A simple file browser for
* selecting the map file is also included. Some preferences can be adjusted via
* the {@link EditPreferences} activity.
*/
public class TileMap extends MapActivity {
// implements ActionBar.OnNavigationListener {
static final String TAG = TileMap.class.getSimpleName();
MapView map;
// private static final String BUNDLE_CENTER_AT_FIRST_FIX =
// "centerAtFirstFix";
private static final String BUNDLE_SHOW_MY_LOCATION = "showMyLocation";
private static final String BUNDLE_SNAP_TO_LOCATION = "snapToLocation";
private static final int DIALOG_ENTER_COORDINATES = 0;
private static final int DIALOG_INFO_MAP_FILE = 1;
// private static final int DIALOG_INFO_MAP_FILE = 1;
private static final int DIALOG_LOCATION_PROVIDER_DISABLED = 2;
// private static final FileFilter FILE_FILTER_EXTENSION_MAP =
// new FilterByFileExtension(".map");
private static final FileFilter FILE_FILTER_EXTENSION_XML =
new FilterByFileExtension(".xml");
// private static final int SELECT_MAP_FILE = 0;
// Intents
private static final int SELECT_RENDER_THEME_FILE = 1;
protected static final int POIS_REQUEST = 2;
LocationHandler mLocation;
private MapDatabases mMapDatabase;
private WakeLock mWakeLock;
MapView mMapView;
private Menu mMenu = null;
SpinnerAdapter mSpinnerAdapter;
POISearch mPoiSearch;
RouteSearch mRouteSearch;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// set up the layout views
setContentView(R.layout.activity_tilemap);
map = (MapView) findViewById(R.id.mapView);
App.map = map;
// configure the MapView and activate the zoomLevel buttons
map.setClickable(true);
// map.setBuiltInZoomControls(true);
map.setFocusable(true);
mLocation = new LocationHandler(this);
// get the pointers to different system services
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager
.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "AMV");
if (savedInstanceState != null) {
if (savedInstanceState.getBoolean(BUNDLE_SHOW_MY_LOCATION)) {
// enableShowMyLocation(savedInstanceState
// .getBoolean(BUNDLE_CENTER_AT_FIRST_FIX));
if (savedInstanceState.getBoolean(BUNDLE_SNAP_TO_LOCATION)) {
mLocation.enableSnapToLocation(false);
}
}
}
App.poiSearch = mPoiSearch = new POISearch(this);
registerForContextMenu(map);
mRouteSearch = new RouteSearch(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
// getMenuInflater().inflate(R.menu.options_menu, menu);
// else
getMenuInflater().inflate(R.menu.options_menu_pre_honeycomb, menu);
getMenuInflater().inflate(R.menu.options_menu, menu);
mMenu = menu;
return true;
@ -97,22 +149,22 @@ public class TileMap extends MapActivity {
return true;
case R.id.menu_rotation_enable:
mMapView.enableRotation(true);
map.enableRotation(true);
toggleMenuRotation();
return true;
case R.id.menu_rotation_disable:
mMapView.enableRotation(false);
map.enableRotation(false);
toggleMenuRotation();
return true;
case R.id.menu_compass_enable:
mMapView.enableCompass(true);
map.enableCompass(true);
toggleMenuRotation();
return true;
case R.id.menu_compass_disable:
mMapView.enableCompass(false);
map.enableCompass(false);
toggleMenuRotation();
return true;
@ -130,10 +182,6 @@ public class TileMap extends MapActivity {
mLocation.disableShowMyLocation());
return true;
// case R.id.menu_position_last_known:
// mLocation.gotoLastKnownPosition();
// return true;
case R.id.menu_position_enter_coordinates:
showDialog(DIALOG_ENTER_COORDINATES);
return true;
@ -145,15 +193,12 @@ public class TileMap extends MapActivity {
case R.id.menu_render_theme:
return true;
case R.id.menu_options:
return true;
case R.id.menu_render_theme_osmarender:
mMapView.setRenderTheme(InternalRenderTheme.OSMARENDER);
map.setRenderTheme(InternalRenderTheme.OSMARENDER);
return true;
case R.id.menu_render_theme_tronrender:
mMapView.setRenderTheme(InternalRenderTheme.TRONRENDER);
map.setRenderTheme(InternalRenderTheme.TRONRENDER);
return true;
case R.id.menu_render_theme_select_file:
@ -162,15 +207,29 @@ public class TileMap extends MapActivity {
// case R.id.menu_position_map_center:
// // disable GPS follow mode if it is enabled
// mLocation.disableSnapToLocation(true);
// location.disableSnapToLocation(true);
//
// mMapView.setCenter(mMapView.getMapDatabase()
// map.setCenter(map.getMapDatabase()
// .getMapInfo().mapCenter);
// return true;
// case R.id.menu_mapfile:
// startMapFilePicker();
// return true;
case R.id.menu_pois:
mPoiSearch.getPOIAsync("bar");
// Intent myIntent = new Intent(this, POIActivity.class);
// myIntent.putParcelableArrayListExtra("POI", mPOIs);
// // myIntent.putExtra("ID", poiMarkers.getBubbledItemId());
// startActivityForResult(myIntent, POIS_REQUEST);
return true;
case R.id.menu_poi_list:
Intent myIntent = new Intent(this, POIActivity.class);
myIntent.putParcelableArrayListExtra("POI", mPoiSearch.mPOIs);
myIntent.putExtra("ID", mPoiSearch.poiMarkers.getBubbledItemId());
startActivityForResult(myIntent, POIS_REQUEST);
return true;
default:
return false;
}
@ -181,12 +240,12 @@ public class TileMap extends MapActivity {
toggleMenuItem(mMenu,
R.id.menu_rotation_enable,
R.id.menu_rotation_disable,
!mMapView.enableRotation);
!map.enableRotation);
toggleMenuItem(mMenu,
R.id.menu_compass_enable,
R.id.menu_compass_disable,
!mMapView.enableCompass);
!map.enableCompass);
}
private static void toggleMenuItem(Menu menu, int id, int id2, boolean enable) {
@ -210,7 +269,7 @@ public class TileMap extends MapActivity {
!mLocation.isShowMyLocationEnabled());
if (mMapDatabase == MapDatabases.MAP_READER) {
menu.findItem(R.id.menu_mapfile).setVisible(true);
//menu.findItem(R.id.menu_mapfile).setVisible(true);
menu.findItem(R.id.menu_position_map_center).setVisible(true);
}
// else {
@ -226,7 +285,7 @@ public class TileMap extends MapActivity {
@Override
public boolean onTrackballEvent(MotionEvent event) {
// forward the event to the MapView
return mMapView.onTrackballEvent(event);
return map.onTrackballEvent(event);
}
// private void startMapFilePicker() {
@ -245,14 +304,40 @@ public class TileMap extends MapActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
switch (requestCode) {
case POIS_REQUEST:
Log.d(TAG, "result: POIS_REQUEST");
if (resultCode == RESULT_OK) {
int id = intent.getIntExtra("ID", 0);
Log.d(TAG, "result: POIS_REQUEST: " + id);
// map.getController().setCenter(mPOIs.get(id).mLocation);
mPoiSearch.poiMarkers.showBubbleOnItem(id, map);
map.getMapViewPosition().animateTo(mPoiSearch.mPOIs.get(id).location);
}
break;
case SELECT_RENDER_THEME_FILE:
if (resultCode == RESULT_OK && intent != null
&& intent.getStringExtra(FilePicker.SELECTED_FILE) != null) {
try {
map.setRenderTheme(intent
.getStringExtra(FilePicker.SELECTED_FILE));
} catch (FileNotFoundException e) {
showToastOnUiThread(e.getLocalizedMessage());
}
}
break;
default:
break;
}
// if (requestCode == SELECT_MAP_FILE) {
// if (resultCode == RESULT_OK) {
//
// mLocation.disableSnapToLocation(true);
// location.disableSnapToLocation(true);
//
// if (intent != null) {
// if (intent.getStringExtra(FilePicker.SELECTED_FILE) != null) {
// mMapView.setMapFile(intent
// map.setMapFile(intent
// .getStringExtra(FilePicker.SELECTED_FILE));
// }
// }
@ -260,122 +345,37 @@ public class TileMap extends MapActivity {
// startActivity(new Intent(this, EditPreferences.class));
// }
// } else
if (requestCode == SELECT_RENDER_THEME_FILE && resultCode == RESULT_OK
&& intent != null
&& intent.getStringExtra(FilePicker.SELECTED_FILE) != null) {
try {
mMapView.setRenderTheme(intent
.getStringExtra(FilePicker.SELECTED_FILE));
} catch (FileNotFoundException e) {
showToastOnUiThread(e.getLocalizedMessage());
}
}
// if (requestCode == SELECT_RENDER_THEME_FILE && resultCode == RESULT_OK
// && intent != null
// && intent.getStringExtra(FilePicker.SELECTED_FILE) != null) {
// try {
// map.setRenderTheme(intent
// .getStringExtra(FilePicker.SELECTED_FILE));
// } catch (FileNotFoundException e) {
// showToastOnUiThread(e.getLocalizedMessage());
// }
// }
}
static boolean isPreHoneyComb() {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB;
}
// @TargetApi(11)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// if (!isPreHoneyComb()) {
// mSpinnerAdapter = ArrayAdapter.createFromResource(this,
// R.array.view_sections,
// android.R.layout.simple_spinner_dropdown_item);
// ActionBar actionBar = getActionBar();
// actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
//
// // actionBar.setListNavigationCallbacks(mSpinnerAdapter, this);
// actionBar.setDisplayShowTitleEnabled(false);
// }
// set up the layout views
setContentView(R.layout.activity_tilemap);
mMapView = (MapView) findViewById(R.id.mapView);
// configure the MapView and activate the zoomLevel buttons
mMapView.setClickable(true);
// mMapView.setBuiltInZoomControls(true);
mMapView.setFocusable(true);
mLocation = new LocationHandler(this);
// get the pointers to different system services
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager
.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "AMV");
if (savedInstanceState != null) {
if (savedInstanceState.getBoolean(BUNDLE_SHOW_MY_LOCATION)) {
// enableShowMyLocation(savedInstanceState
// .getBoolean(BUNDLE_CENTER_AT_FIRST_FIX));
if (savedInstanceState.getBoolean(BUNDLE_SNAP_TO_LOCATION)) {
mLocation.enableSnapToLocation(false);
}
}
}
}
@Override
protected Dialog onCreateDialog(int id) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
if (id == DIALOG_ENTER_COORDINATES) {
builder.setIcon(android.R.drawable.ic_menu_mylocation);
builder.setTitle(R.string.menu_position_enter_coordinates);
LayoutInflater factory = LayoutInflater.from(this);
final View view = factory.inflate(R.layout.dialog_enter_coordinates, null);
builder.setView(view);
if (mLocationDialog == null)
mLocationDialog = new LocationDialog();
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
mLocation.disableSnapToLocation(true);
return mLocationDialog.createDialog(this);
// 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);
byte zoom = (byte) (zoomLevelView.getProgress());
MapPosition mapPosition = new MapPosition(latitude,
longitude, zoom, 1, 0);
TileMap.this.mMapView.setMapCenter(mapPosition);
}
});
builder.setNegativeButton(R.string.cancel, null);
return builder.create();
} 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 if (id == DIALOG_INFO_MAP_FILE) {
builder.setIcon(android.R.drawable.ic_menu_info_details);
builder.setTitle(R.string.menu_info_map_file);
LayoutInflater factory = LayoutInflater.from(this);
builder.setView(factory.inflate(R.layout.dialog_info_map_file, null));
builder.setPositiveButton(R.string.ok, null);
return builder.create();
} else {
// no dialog will be created
return null;
@ -390,6 +390,7 @@ public class TileMap extends MapActivity {
@Override
protected void onPause() {
Log.d(TAG, "onPause");
super.onPause();
// release the wake lock if necessary
if (mWakeLock.isHeld()) {
@ -397,91 +398,14 @@ public class TileMap extends MapActivity {
}
}
LocationDialog mLocationDialog;
@Override
protected void onPrepareDialog(int id, final Dialog dialog) {
if (id == DIALOG_ENTER_COORDINATES) {
EditText editText = (EditText) dialog.findViewById(R.id.latitude);
GeoPoint mapCenter = mMapView.getMapPosition().getMapCenter();
editText.setText(Double.toString(mapCenter.getLatitude()));
editText = (EditText) dialog.findViewById(R.id.longitude);
editText.setText(Double.toString(mapCenter.getLongitude()));
mLocationDialog.prepareDialog(map, dialog);
SeekBar zoomlevel = (SeekBar) dialog.findViewById(R.id.zoomLevel);
zoomlevel.setMax(20); // FIXME
// mMapView.getMapGenerator().getZoomLevelMax());
zoomlevel.setProgress(mMapView.getMapPosition().getZoomLevel());
final TextView textView = (TextView) dialog.findViewById(R.id.zoomlevelValue);
textView.setText(String.valueOf(zoomlevel.getProgress()));
zoomlevel.setOnSeekBarChangeListener(new SeekBarChangeListener(textView));
// } else if (id == DIALOG_INFO_MAP_FILE) {
// MapInfo mapInfo = mMapView.getMapDatabase().getMapInfo();
//
// TextView textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewName);
// textView.setText(mMapView.getMapFile());
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewSize);
// textView.setText(FileUtils.formatFileSize(mapInfo.fileSize,
// getResources()));
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewVersion);
// textView.setText(String.valueOf(mapInfo.fileVersion));
//
// // textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewDebug);
// // if (mapFileInfo.debugFile) {
// // textView.setText(R.string.info_map_file_debug_yes);
// // } else {
// // textView.setText(R.string.info_map_file_debug_no);
// // }
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewDate);
// Date date = new Date(mapInfo.mapDate);
// textView.setText(DateFormat.getDateTimeInstance().format(date));
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewArea);
// BoundingBox boundingBox = mapInfo.boundingBox;
// textView.setText(boundingBox.getMinLatitude() + ", "
// + boundingBox.getMinLongitude() + " - \n"
// + boundingBox.getMaxLatitude() + ", " +
// boundingBox.getMaxLongitude());
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewStartPosition);
// GeoPoint startPosition = mapInfo.startPosition;
// if (startPosition == null) {
// textView.setText(null);
// } else {
// textView.setText(startPosition.getLatitude() + ", "
// + startPosition.getLongitude());
// }
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewStartZoomLevel);
// Byte startZoomLevel = mapInfo.startZoomLevel;
// if (startZoomLevel == null) {
// textView.setText(null);
// } else {
// textView.setText(startZoomLevel.toString());
// }
//
// textView = (TextView) dialog
// .findViewById(R.id.infoMapFileViewLanguagePreference);
// textView.setText(mapInfo.languagePreference);
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewComment);
// textView.setText(mapInfo.comment);
//
// textView = (TextView)
// dialog.findViewById(R.id.infoMapFileViewCreatedBy);
// textView.setText(mapInfo.createdBy);
} else {
super.onPrepareDialog(id, dialog);
}
@ -503,24 +427,6 @@ public class TileMap extends MapActivity {
// scaleBarUnitDefault);
// mapScaleBar.setImperialUnits(scaleBarUnit.equals("imperial"));
// if (preferences.contains("mapGenerator")) {
// String name = preferences.getString("mapGenerator",
// MapGeneratorInternal.SW_RENDERER.name());
// MapGeneratorInternal mapGeneratorInternalNew;
// try {
// mapGeneratorInternalNew = MapGeneratorInternal.valueOf(name);
// } catch (IllegalArgumentException e) {
// mapGeneratorInternalNew = MapGeneratorInternal.SW_RENDERER;
// }
//
// if (mapGeneratorInternalNew != mapGeneratorInternal) {
// TileGenerator mapGenerator =
// MapGeneratorFactory.createMapGenerator(mapGeneratorInternalNew);
// mapView.setMapGenerator(mapGenerator);
// mapGeneratorInternal = mapGeneratorInternalNew;
// }
// }
if (preferences.contains("mapDatabase")) {
String name = preferences.getString("mapDatabase",
MapDatabases.PBMAP_READER.name());
@ -533,10 +439,10 @@ public class TileMap extends MapActivity {
mapDatabaseNew = MapDatabases.PBMAP_READER;
}
Log.d("VectorTileMap", "set map database " + mapDatabaseNew);
if (mapDatabaseNew != mMapDatabase) {
mMapView.setMapDatabase(mapDatabaseNew);
Log.d(TAG, "set map database " + mapDatabaseNew);
map.setMapDatabase(mapDatabaseNew);
mMapDatabase = mapDatabaseNew;
}
}
@ -544,10 +450,10 @@ public class TileMap extends MapActivity {
// try {
// String textScaleDefault =
// getString(R.string.preferences_text_scale_default);
// mMapView.setTextScale(Float.parseFloat(preferences.getString("textScale",
// map.setTextScale(Float.parseFloat(preferences.getString("textScale",
// textScaleDefault)));
// } catch (NumberFormatException e) {
// mMapView.setTextScale(1);
// map.setTextScale(1);
// }
if (preferences.getBoolean("fullscreen", false)) {
@ -579,30 +485,29 @@ public class TileMap extends MapActivity {
boolean drawUnmatchedWays =
preferences.getBoolean("drawUnmatchedWays", false);
DebugSettings debugSettings = new DebugSettings(drawTileCoordinates,
drawTileFrames, disablePolygons, drawUnmatchedWays);
DebugSettings cur = map.getDebugSettings();
if (cur.mDisablePolygons != disablePolygons
|| cur.mDrawTileCoordinates != drawTileCoordinates
|| cur.mDrawTileFrames != drawTileFrames
|| cur.mDrawUnmatchted != drawUnmatchedWays) {
Log.d(TAG, "set map debug settings");
mMapView.setDebugSettings(debugSettings);
DebugSettings debugSettings = new DebugSettings(drawTileCoordinates,
drawTileFrames, disablePolygons, drawUnmatchedWays);
map.setDebugSettings(debugSettings);
}
// if (mMapDatabase == MapDatabases.MAP_READER) {
// if (mMapView.getMapFile() == null)
// if (map.getMapFile() == null)
// startMapFilePicker();
// } else {
// mMapView.setMapFile(mMapView.getMapFile());
// map.setMapFile(map.getMapFile());
// }
// if (Build.VERSION.SDK_INT >= 11) {
// VersionHelper.refreshActionBarMenu(this);
// }
map.redrawMap();
}
// static class VersionHelper {
// @TargetApi(11)
// static void refreshActionBarMenu(Activity activity) {
// activity.invalidateOptionsMenu();
// }
// }
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@ -616,7 +521,6 @@ public class TileMap extends MapActivity {
/**
* Uses the UI thread to display the given text message as toast
* notification.
*
* @param text
* the text message to display
*/
@ -636,33 +540,25 @@ public class TileMap extends MapActivity {
}
}
// @Override
// public boolean onNavigationItemSelected(int arg0, long arg1) {
// // TODO Auto-generated method stub
// return false;
// }
//----------- Context Menu when clicking on the map
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
Log.d(TAG, "create context menu");
class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
private final TextView textView;
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.map_menu, menu);
}
SeekBarChangeListener(TextView textView) {
this.textView = textView;
}
@Override
public boolean onContextItemSelected(MenuItem item) {
Log.d(TAG, "context menu item selected " + item.getItemId());
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
this.textView.setText(String.valueOf(progress));
}
if (mRouteSearch.onContextItemSelected(item))
return true;
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// do nothing
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// do nothing
}
return super.onContextItemSelected(item);
}
}

View File

@ -29,14 +29,16 @@ 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
* 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 currentValueTextView;
private Editor editor;
private SeekBar preferenceSeekBar;
private TextView mCurrentValueTextView;
private Editor mEditor;
private SeekBar mPreferenceSeekBar;
/**
* How much the value should increase when the seek bar is moved.
@ -65,7 +67,6 @@ abstract class SeekBarPreference extends DialogPreference implements OnSeekBarCh
/**
* Create a new seek bar preference.
*
* @param context
* the context of the seek bar preferences activity.
* @param attrs
@ -73,26 +74,26 @@ abstract class SeekBarPreference extends DialogPreference implements OnSeekBarCh
*/
SeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
this.preferencesDefault = PreferenceManager.getDefaultSharedPreferences(context);
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
&& this.seekBarCurrentValue != this.preferenceSeekBar.getProgress()) {
&& seekBarCurrentValue != mPreferenceSeekBar.getProgress()) {
// get the value of the seek bar and save it in the preferences
this.seekBarCurrentValue = this.preferenceSeekBar.getProgress();
this.editor = this.preferencesDefault.edit();
this.editor.putInt(this.getKey(), this.seekBarCurrentValue);
this.editor.commit();
seekBarCurrentValue = mPreferenceSeekBar.getProgress();
mEditor = preferencesDefault.edit();
mEditor.putInt(getKey(), seekBarCurrentValue);
mEditor.commit();
}
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (this.currentValueTextView != null) {
this.currentValueTextView.setText(getCurrentValueText(progress));
if (mCurrentValueTextView != null) {
mCurrentValueTextView.setText(getCurrentValueText(progress));
}
}
@ -114,38 +115,37 @@ abstract class SeekBarPreference extends DialogPreference implements OnSeekBarCh
linearLayout.setPadding(20, 10, 20, 10);
// check if a text message should appear above the seek bar
if (this.messageText != null) {
if (messageText != null) {
// create a text view for the text messageText
TextView messageTextView = new TextView(getContext());
messageTextView.setText(this.messageText);
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
this.preferenceSeekBar = new SeekBar(getContext());
this.preferenceSeekBar.setOnSeekBarChangeListener(this);
this.preferenceSeekBar.setMax(this.max);
this.preferenceSeekBar.setProgress(Math.min(this.seekBarCurrentValue, this.max));
this.preferenceSeekBar.setKeyProgressIncrement(this.increment);
this.preferenceSeekBar.setPadding(0, 0, 0, 10);
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(this.preferenceSeekBar);
linearLayout.addView(mPreferenceSeekBar);
// create the text view for the current value below the seek bar
this.currentValueTextView = new TextView(getContext());
this.currentValueTextView.setText(getCurrentValueText(this.preferenceSeekBar.getProgress()));
this.currentValueTextView.setGravity(Gravity.CENTER_HORIZONTAL);
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(this.currentValueTextView);
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

View File

@ -16,40 +16,46 @@ package org.oscim.core;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import android.os.Parcel;
import android.os.Parcelable;
/**
* A BoundingBox represents an immutable set of two latitude and two longitude coordinates.
* A BoundingBox represents an immutable set of two latitude and two longitude
* coordinates.
*/
public class BoundingBox implements Serializable {
public class BoundingBox implements Parcelable {
/**
* Conversion factor from degrees to microdegrees.
*/
private static final double CONVERSION_FACTOR = 1000000d;
private static final long serialVersionUID = 1L;
private static boolean isBetween(int number, int min, int max) {
return min <= number && number <= max;
}
/**
* The maximum latitude value of this BoundingBox in microdegrees (degrees * 10^6).
* The maximum latitude value of this BoundingBox in microdegrees (degrees *
* 10^6).
*/
public final int maxLatitudeE6;
/**
* The maximum longitude value of this BoundingBox in microdegrees (degrees * 10^6).
* The maximum longitude value of this BoundingBox in microdegrees (degrees
* * 10^6).
*/
public final int maxLongitudeE6;
/**
* The minimum latitude value of this BoundingBox in microdegrees (degrees * 10^6).
* The minimum latitude value of this BoundingBox in microdegrees (degrees *
* 10^6).
*/
public final int minLatitudeE6;
/**
* The minimum longitude value of this BoundingBox in microdegrees (degrees * 10^6).
* The minimum longitude value of this BoundingBox in microdegrees (degrees
* * 10^6).
*/
public final int minLongitudeE6;
@ -76,10 +82,20 @@ public class BoundingBox implements Serializable {
this.hashCodeValue = calculateHashCode();
}
public BoundingBox(double minLatitude, double minLongitude, double maxLatitude,
double maxLongitude) {
this.minLatitudeE6 = (int) (minLatitude * 1E6);
this.minLongitudeE6 = (int) (minLongitude * 1E6);
this.maxLatitudeE6 = (int) (maxLatitude * 1E6);
this.maxLongitudeE6 = (int) (maxLongitude * 1E6);
this.hashCodeValue = calculateHashCode();
}
/**
* @param geoPoint
* the point whose coordinates should be checked.
* @return true if this BoundingBox contains the given GeoPoint, false otherwise.
* @return true if this BoundingBox contains the given GeoPoint, false
* otherwise.
*/
public boolean contains(GeoPoint geoPoint) {
return isBetween(geoPoint.latitudeE6, this.minLatitudeE6, this.maxLatitudeE6)
@ -107,12 +123,14 @@ public class BoundingBox implements Serializable {
}
/**
* @return the GeoPoint at the horizontal and vertical center of this BoundingBox.
* @return the GeoPoint at the horizontal and vertical center of this
* BoundingBox.
*/
public GeoPoint getCenterPoint() {
int latitudeOffset = (this.maxLatitudeE6 - this.minLatitudeE6) / 2;
int longitudeOffset = (this.maxLongitudeE6 - this.minLongitudeE6) / 2;
return new GeoPoint(this.minLatitudeE6 + latitudeOffset, this.minLongitudeE6 + longitudeOffset);
return new GeoPoint(this.minLatitudeE6 + latitudeOffset, this.minLongitudeE6
+ longitudeOffset);
}
/**
@ -175,8 +193,78 @@ public class BoundingBox implements Serializable {
return result;
}
private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
private void readObject(ObjectInputStream objectInputStream) throws IOException,
ClassNotFoundException {
objectInputStream.defaultReadObject();
this.hashCodeValue = calculateHashCode();
}
/* code below is from osdmroid, @author Nicolas Gramlich */
public static BoundingBox fromGeoPoints(final ArrayList<? extends GeoPoint> partialPolyLine) {
int minLat = Integer.MAX_VALUE;
int minLon = Integer.MAX_VALUE;
int maxLat = Integer.MIN_VALUE;
int maxLon = Integer.MIN_VALUE;
for (final GeoPoint gp : partialPolyLine) {
minLat = Math.min(minLat, gp.latitudeE6);
minLon = Math.min(minLon, gp.longitudeE6);
maxLat = Math.max(maxLat, gp.latitudeE6);
maxLon = Math.max(maxLon, gp.longitudeE6);
}
return new BoundingBox(minLat, minLon, maxLat, maxLon);
}
public static final Parcelable.Creator<BoundingBox> CREATOR = new Parcelable.Creator<BoundingBox>() {
@Override
public BoundingBox createFromParcel(final Parcel in) {
return new BoundingBox(in.readInt(), in.readInt(), in.readInt(), in.readInt());
}
@Override
public BoundingBox[] newArray(final int size) {
return new BoundingBox[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(minLatitudeE6);
dest.writeInt(minLongitudeE6);
dest.writeInt(maxLatitudeE6);
dest.writeInt(maxLongitudeE6);
}
// public BoundingBox(final double north, final double east, final double south,
// final double west) {
// this((int) (north * 1E6), (int) (east * 1E6), (int) (south * 1E6), (int) (west * 1E6));
// // this.mLatNorthE6 = (int) (north * 1E6);
// // this.mLonEastE6 = (int) (east * 1E6);
// // this.mLatSouthE6 = (int) (south * 1E6);
// // this.mLonWestE6 = (int) (west * 1E6);
// }
// public int getLatNorthE6() {
// return this.maxLatitudeE6;
// }
//
// public int getLatSouthE6() {
// return this.minLatitudeE6;
// }
//
// public int getLonEastE6() {
// return this.maxLongitudeE6;
// }
//
// public int getLonWestE6() {
// return this.minLongitudeE6;
// }
}

View File

@ -1,6 +1,8 @@
/*
* Copyright 2010, 2011, 2012 mapsforge.org
*
* Copyright 2012 osmdroid authors: Nicolas Gramlich, Theodore Hong
* 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.
@ -14,10 +16,14 @@
*/
package org.oscim.core;
import android.os.Parcel;
import android.os.Parcelable;
/**
* A GeoPoint represents an immutable pair of latitude and longitude coordinates.
* A GeoPoint represents an immutable pair of latitude and longitude
* coordinates.
*/
public class GeoPoint implements Comparable<GeoPoint> {
public class GeoPoint implements Parcelable, Comparable<GeoPoint> {
/**
* Conversion factor from degrees to microdegrees.
*/
@ -33,6 +39,8 @@ public class GeoPoint implements Comparable<GeoPoint> {
*/
public final int longitudeE6;
// public final int altitude;
/**
* The hash code of this object.
*/
@ -40,9 +48,11 @@ public class GeoPoint implements Comparable<GeoPoint> {
/**
* @param latitude
* the latitude in degrees, will be limited to the possible latitude range.
* the latitude in degrees, will be limited to the possible
* latitude range.
* @param longitude
* the longitude in degrees, will be limited to the possible longitude range.
* the longitude in degrees, will be limited to the possible
* longitude range.
*/
public GeoPoint(double latitude, double longitude) {
double limitLatitude = MercatorProjection.limitLatitude(latitude);
@ -50,14 +60,15 @@ public class GeoPoint implements Comparable<GeoPoint> {
double limitLongitude = MercatorProjection.limitLongitude(longitude);
this.longitudeE6 = (int) (limitLongitude * CONVERSION_FACTOR);
}
/**
* @param latitudeE6
* the latitude in microdegrees (degrees * 10^6), will be limited to the possible latitude range.
* the latitude in microdegrees (degrees * 10^6), will be limited
* to the possible latitude range.
* @param longitudeE6
* the longitude in microdegrees (degrees * 10^6), will be limited to the possible longitude range.
* the longitude in microdegrees (degrees * 10^6), will be
* limited to the possible longitude range.
*/
public GeoPoint(int latitudeE6, int longitudeE6) {
this(latitudeE6 / CONVERSION_FACTOR, longitudeE6 / CONVERSION_FACTOR);
@ -135,4 +146,66 @@ public class GeoPoint implements Comparable<GeoPoint> {
result = 31 * result + this.longitudeE6;
return result;
}
// ===========================================================
// Methods from osmdroid
// ===========================================================
public static final float DEG2RAD = (float) (Math.PI / 180.0);
public static final float RAD2DEG = (float) (180.0 / Math.PI);
// http://en.wikipedia.org/wiki/Earth_radius#Equatorial_radius
public static final int RADIUS_EARTH_METERS = 6378137;
/**
* @see "http://www.geocities.com/DrChengalva/GPSDistance.html"
* @param other
* ...
* @return distance in meters
*/
public int distanceTo(final GeoPoint other) {
double a1 = DEG2RAD * latitudeE6 / 1E6;
double a2 = DEG2RAD * longitudeE6 / 1E6;
double b1 = DEG2RAD * other.latitudeE6 / 1E6;
double b2 = DEG2RAD * other.longitudeE6 / 1E6;
double cosa1 = Math.cos(a1);
double cosb1 = Math.cos(b1);
double t1 = cosa1 * Math.cos(a2) * cosb1 * Math.cos(b2);
double t2 = cosa1 * Math.sin(a2) * cosb1 * Math.sin(b2);
double t3 = Math.sin(a1) * Math.sin(b1);
double tt = Math.acos(t1 + t2 + t3);
return (int) (RADIUS_EARTH_METERS * tt);
}
// ===========================================================
// Parcelable
// ===========================================================
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel out, final int flags) {
out.writeInt(latitudeE6);
out.writeInt(longitudeE6);
}
public static final Parcelable.Creator<GeoPoint> CREATOR = new Parcelable.Creator<GeoPoint>() {
@Override
public GeoPoint createFromParcel(final Parcel in) {
return new GeoPoint(in.readInt(), in.readInt());
}
@Override
public GeoPoint[] newArray(final int size) {
return new GeoPoint[size];
}
};
}

View File

@ -1,5 +1,6 @@
/*
* 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
@ -16,9 +17,7 @@ package org.oscim.core;
import android.opengl.Matrix;
/**
* A MapPosition Container.
*/
/** A MapPosition Container. */
public class MapPosition {
public double lon;
@ -33,7 +32,9 @@ public class MapPosition {
public double y;
public float[] viewMatrix;
public float[] rotateMatrix;
// // DO NOT MODIFY! shared with MapViewPosition
// public float[] projMatrix;
public MapPosition() {
this.zoomLevel = (byte) 1;
@ -49,19 +50,32 @@ public class MapPosition {
public void init() {
viewMatrix = new float[16];
Matrix.setIdentityM(viewMatrix, 0);
rotateMatrix = new float[16];
Matrix.setIdentityM(rotateMatrix, 0);
//
// rotateMatrix = new float[16];
// Matrix.setIdentityM(rotateMatrix, 0);
}
/**
* @param geoPoint
// public Point geopointToMap(GeoPoint in, Point reuse) {
// Point out = reuse == null ? new Point() : reuse;
// out.x = (int) (MercatorProjection.longitudeToPixelX(in.getLongitude(), zoomLevel) - x);
// out.y = (int) (MercatorProjection.latitudeToPixelY(in.getLatitude(), zoomLevel) - y);
//
// return out;
// }
// public void geopointToMap(GeoPoint in, float[] out, int pos) {
// out[pos * 2 + 0] =
// (float) (MercatorProjection.longitudeToPixelX(in.getLongitude(), zoomLevel) - x);
// out[pos * 2 + 1] =
// (float) (MercatorProjection.latitudeToPixelY(in.getLatitude(), zoomLevel) - y);
// }
/** @param geoPoint
* the map position.
* @param zoomLevel
* the zoom level.
* @param scale
* ...
*/
* ... */
public MapPosition(GeoPoint geoPoint, byte zoomLevel, float scale) {
this.zoomLevel = zoomLevel;
this.scale = scale;

View File

@ -1,5 +1,6 @@
/*
* 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
@ -14,6 +15,7 @@
*/
package org.oscim.core;
import android.graphics.Point;
/**
* An implementation of the spherical Mercator projection.
@ -246,4 +248,13 @@ public final class MercatorProjection {
private MercatorProjection() {
throw new IllegalStateException();
}
public static Point projectPoint(GeoPoint geopoint, byte z, Point reuse) {
Point out = reuse == null ? new Point() : reuse;
out.x = (int) MercatorProjection.longitudeToPixelX(geopoint.getLongitude(), z);
out.y = (int) MercatorProjection.latitudeToPixelY(geopoint.getLatitude(), z);
return out;
}
}

View File

@ -38,7 +38,6 @@ public class JobQueue {
* the job to be added to this queue.
*/
public synchronized void setJobs(ArrayList<JobTile> tiles) {
// mPriorityQueue.clear();
for (int i = 0, n = tiles.size(); i < n; i++) {
JobTile tile = tiles.get(i);
@ -51,9 +50,10 @@ public class JobQueue {
* Removes all jobs from this queue.
*/
public synchronized void clear() {
for (JobTile tile : mPriorityQueue) {
tile.isLoading = false;
}
JobTile t;
while ((t = mPriorityQueue.poll()) != null)
t.isLoading = false;
mPriorityQueue.clear();
}

View File

@ -0,0 +1,159 @@
package org.oscim.overlay;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
public class DefaultResourceProxyImpl implements ResourceProxy, MapViewConstants {
// private static final String TAG = DefaultResourceProxyImpl.class.getSimpleName();
private DisplayMetrics mDisplayMetrics;
/**
* Constructor.
*
* @param pContext
* Used to get the display metrics that are used for scaling the
* bitmaps returned by {@@link getBitmap}. Can be null,
* in which case the bitmaps are not scaled.
*/
public DefaultResourceProxyImpl(final Context pContext) {
if (pContext != null) {
mDisplayMetrics = pContext.getResources().getDisplayMetrics();
// if (DEBUGMODE) {
// logger.debug("mDisplayMetrics=" + mDisplayMetrics);
// }
}
}
@Override
public String getString(final string pResId) {
switch (pResId) {
case mapnik:
return "Mapnik";
case cyclemap:
return "Cycle Map";
case public_transport:
return "Public transport";
case base:
return "OSM base layer";
case topo:
return "Topographic";
case hills:
return "Hills";
case cloudmade_standard:
return "CloudMade (Standard tiles)";
case cloudmade_small:
return "CloudMade (small tiles)";
case mapquest_osm:
return "Mapquest";
case mapquest_aerial:
return "Mapquest Aerial";
case bing:
return "Bing";
case fiets_nl:
return "OpenFietsKaart overlay";
case base_nl:
return "Netherlands base overlay";
case roads_nl:
return "Netherlands roads overlay";
case unknown:
return "Unknown";
case format_distance_meters:
return "%s m";
case format_distance_kilometers:
return "%s km";
case format_distance_miles:
return "%s mi";
case format_distance_nautical_miles:
return "%s nm";
case format_distance_feet:
return "%s ft";
case online_mode:
return "Online mode";
case offline_mode:
return "Offline mode";
case my_location:
return "My location";
case compass:
return "Compass";
case map_mode:
return "Map mode";
default:
throw new IllegalArgumentException();
}
}
@Override
public String getString(final string pResId, final Object... formatArgs) {
return String.format(getString(pResId), formatArgs);
}
@Override
public Bitmap getBitmap(final bitmap pResId) {
InputStream is = null;
try {
final String resName = pResId.name() + ".png";
is = getClass().getResourceAsStream(resName);
if (is == null) {
throw new IllegalArgumentException("Resource not found: " + resName);
}
BitmapFactory.Options options = null;
if (mDisplayMetrics != null) {
options = getBitmapOptions();
}
return BitmapFactory.decodeStream(is, null, options);
} catch (final OutOfMemoryError e) {
// logger.error("OutOfMemoryError getting bitmap resource: " +
// pResId);
System.gc();
// there's not much we can do here
// - when we load a bitmap from resources we expect it to be found
throw e;
} finally {
if (is != null) {
try {
is.close();
} catch (final IOException ignore) {
}
}
}
}
private BitmapFactory.Options getBitmapOptions() {
try {
final Field density = DisplayMetrics.class.getDeclaredField("DENSITY_DEFAULT");
final Field inDensity = BitmapFactory.Options.class.getDeclaredField("inDensity");
final Field inTargetDensity = BitmapFactory.Options.class
.getDeclaredField("inTargetDensity");
final Field targetDensity = DisplayMetrics.class.getDeclaredField("densityDpi");
final BitmapFactory.Options options = new BitmapFactory.Options();
inDensity.setInt(options, density.getInt(null));
inTargetDensity.setInt(options, targetDensity.getInt(mDisplayMetrics));
return options;
} catch (final IllegalAccessException ex) {
// ignore
} catch (final NoSuchFieldException ex) {
// ignore
}
return null;
}
@Override
public Drawable getDrawable(final bitmap pResId) {
return new BitmapDrawable(getBitmap(pResId));
}
@Override
public float getDisplayMetricsDensity() {
return mDisplayMetrics.density;
}
}

View File

@ -12,21 +12,20 @@
* 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.view;
package org.oscim.overlay;
import android.os.Handler;
import android.os.Message;
import org.oscim.renderer.overlays.RenderOverlay;
import org.oscim.view.MapView;
public class DelayedTaskHandler extends Handler {
public final int MESSAGE_UPDATE_POSITION = 1;
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_UPDATE_POSITION:
break;
}
public class GenericOverlay extends Overlay {
/**
* @param mapView
* ...
* @param renderer
* ...
*/
public GenericOverlay(MapView mapView, RenderOverlay renderer) {
super();
mLayer = renderer;
}
}

View File

@ -0,0 +1,278 @@
package org.oscim.overlay;
import java.util.List;
import org.oscim.core.MercatorProjection;
import org.oscim.overlay.ResourceProxy.bitmap;
import org.oscim.view.MapView;
import org.oscim.view.MapViewPosition;
import android.content.Context;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
public class ItemizedIconOverlay<Item extends OverlayItem> extends ItemizedOverlay<Item> {
private static final String TAG = ItemizedIconOverlay.class.getSimpleName();
protected final List<Item> mItemList;
protected OnItemGestureListener<Item> mOnItemGestureListener;
private int mDrawnItemsLimit = Integer.MAX_VALUE;
private final Point mTouchScreenPoint = new Point();
private final Point mItemPoint = new Point();
public ItemizedIconOverlay(
final MapView mapView,
final List<Item> pList,
final Drawable pDefaultMarker,
final ItemizedIconOverlay.OnItemGestureListener<Item> pOnItemGestureListener,
final ResourceProxy pResourceProxy) {
super(mapView, pDefaultMarker, pResourceProxy);
this.mItemList = pList;
this.mOnItemGestureListener = pOnItemGestureListener;
populate();
}
public ItemizedIconOverlay(
final MapView mapView,
final List<Item> pList,
final ItemizedIconOverlay.OnItemGestureListener<Item> pOnItemGestureListener,
final ResourceProxy pResourceProxy) {
this(mapView, pList, pResourceProxy.getDrawable(bitmap.marker_default),
pOnItemGestureListener,
pResourceProxy);
}
public ItemizedIconOverlay(
final MapView mapView,
final Context pContext,
final List<Item> pList,
final ItemizedIconOverlay.OnItemGestureListener<Item> pOnItemGestureListener) {
this(mapView, pList, new DefaultResourceProxyImpl(pContext)
.getDrawable(bitmap.marker_default),
pOnItemGestureListener, new DefaultResourceProxyImpl(pContext));
}
@Override
public boolean onSnapToItem(final int pX, final int pY, final Point pSnapPoint,
final MapView pMapView) {
// TODO Implement this!
return false;
}
@Override
protected Item createItem(final int index) {
return mItemList.get(index);
}
@Override
public int size() {
return Math.min(mItemList.size(), mDrawnItemsLimit);
}
public boolean addItem(final Item item) {
final boolean result = mItemList.add(item);
populate();
return result;
}
public void addItem(final int location, final Item item) {
mItemList.add(location, item);
}
public boolean addItems(final List<Item> items) {
final boolean result = mItemList.addAll(items);
populate();
return result;
}
public void removeAllItems() {
removeAllItems(true);
}
public void removeAllItems(final boolean withPopulate) {
mItemList.clear();
if (withPopulate) {
populate();
}
}
public boolean removeItem(final Item item) {
final boolean result = mItemList.remove(item);
populate();
return result;
}
public Item removeItem(final int position) {
final Item result = mItemList.remove(position);
populate();
return result;
}
/**
* Each of these methods performs a item sensitive check. If the item is
* located its corresponding method is called. The result of the call is
* returned. Helper methods are provided so that child classes may more
* easily override behavior without resorting to overriding the
* ItemGestureListener methods.
*/
@Override
public boolean onSingleTapUp(final MotionEvent event, final MapView mapView) {
return (activateSelectedItems(event, mapView, new ActiveItem() {
@Override
public boolean run(final int index) {
final ItemizedIconOverlay<Item> that = ItemizedIconOverlay.this;
if (that.mOnItemGestureListener == null) {
return false;
}
return onSingleTapUpHelper(index, that.mItemList.get(index), mapView);
}
})) || super.onSingleTapUp(event, mapView);
}
/**
* @param index
* ...
* @param item
* ...
* @param mapView
* ...
* @return ...
*/
protected boolean onSingleTapUpHelper(final int index, final Item item, final MapView mapView) {
return this.mOnItemGestureListener.onItemSingleTapUp(index, item);
}
@Override
public boolean onLongPress(final MotionEvent event, final MapView mapView) {
Log.d(TAG, "onLongPress");
return (activateSelectedItems(event, mapView, new ActiveItem() {
@Override
public boolean run(final int index) {
final ItemizedIconOverlay<Item> that = ItemizedIconOverlay.this;
if (that.mOnItemGestureListener == null) {
return false;
}
return onLongPressHelper(index, getItem(index));
}
})) || super.onLongPress(event, mapView);
}
protected boolean onLongPressHelper(final int index, final Item item) {
return this.mOnItemGestureListener.onItemLongPress(index, item);
}
/**
* When a content sensitive action is performed the content item needs to be
* identified. This method does that and then performs the assigned task on
* that item.
*
* @param event
* ...
* @param mapView
* ...
* @param task
* ..
* @return true if event is handled false otherwise
*/
private boolean activateSelectedItems(final MotionEvent event, final MapView mapView,
final ActiveItem task) {
// final Projection pj = mapView.getProjection();
final int eventX = (int) event.getX();
final int eventY = (int) event.getY();
// Log.d("...", "test items " + eventX + " " + eventY);
/* These objects are created to avoid construct new ones every cycle. */
// pj.fromMapPixels(eventX, eventY, mTouchScreenPoint);
MapViewPosition mapViewPosition = mMapView.getMapViewPosition();
byte z = mapViewPosition.getMapPosition().zoomLevel;
mapViewPosition.getScreenPointOnMap(eventX, eventY, mTouchScreenPoint);
int nearest = -1;
float dist = Float.MAX_VALUE;
// TODO use intermediate projection and bounding box test
for (int i = 0; i < this.mItemList.size(); ++i) {
final Item item = getItem(i);
// final Drawable marker = (item.getMarker(0) == null) ? this.mDefaultMarker : item
// .getMarker(0);
// int x = (int) MercatorProjection.longitudeToPixelX(item.getPoint().getLongitude(), z);
// int y = (int) MercatorProjection.latitudeToPixelY(item.getPoint().getLatitude(), z);
MercatorProjection.projectPoint(item.getPoint(), z, mItemPoint);
// pj.toPixels(item.getPoint(), mItemPoint);
// Log.d("...", (x - mTouchScreenPoint.x) + " " + (y - mTouchScreenPoint.y));
float dx = mItemPoint.x - mTouchScreenPoint.x;
float dy = mItemPoint.y - mTouchScreenPoint.y;
float d = FloatMath.sqrt(dx * dx + dy * dy);
if (d < 50) {
// Log.d("...", "HIT! " + (x - mTouchScreenPoint.x) + " " + (y - mTouchScreenPoint.y));
if (d < dist) {
dist = d;
nearest = i;
}
// if (hitTest(item, marker, mTouchScreenPoint.x - mItemPoint.x, mTouchScreenPoint.y
// - mItemPoint.y)) {
}
}
if (nearest >= 0 && task.run(nearest)) {
return true;
}
return false;
}
// ===========================================================
// Getter & Setter
// ===========================================================
public int getDrawnItemsLimit() {
return this.mDrawnItemsLimit;
}
public void setDrawnItemsLimit(final int aLimit) {
this.mDrawnItemsLimit = aLimit;
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
/**
* When the item is touched one of these methods may be invoked depending on
* the type of touch. Each of them returns true if the event was completely
* handled.
*
* @param <T>
* ....
*/
public static interface OnItemGestureListener<T> {
public boolean onItemSingleTapUp(final int index, final T item);
public boolean onItemLongPress(final int index, final T item);
}
public static interface ActiveItem {
public boolean run(final int aIndex);
}
}

View File

@ -0,0 +1,484 @@
// Created by plusminus on 23:18:23 - 02.10.2008
package org.oscim.overlay;
import org.oscim.core.GeoPoint;
import org.oscim.core.MapPosition;
import org.oscim.core.MercatorProjection;
import org.oscim.overlay.OverlayItem.HotspotPlace;
import org.oscim.renderer.layer.SymbolLayer;
import org.oscim.renderer.overlays.RenderOverlay;
import org.oscim.view.MapView;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.opengl.Matrix;
/**
* Draws a list of {@link OverlayItem} as markers to a map. The item with the
* lowest index is drawn as last and therefore the 'topmost' marker. It also
* gets checked for onTap first. This class is generic, because you then you get
* your custom item-class passed back in onTap().
* @author Marc Kurtz
* @author Nicolas Gramlich
* @author Theodore Hong
* @author Fred Eisele
* @author Hannes Janetzek
* @param <Item>
* ...
*/
public abstract class ItemizedOverlay<Item extends OverlayItem> extends Overlay implements
Overlay.Snappable {
protected final Drawable mDefaultMarker;
protected boolean mDrawFocusedItem = true;
protected MapView mMapView;
class InternalItem {
InternalItem next;
Item item;
boolean visible;
boolean changes;
int x, y, px, py;
}
/* package */InternalItem mItems;
/* package */Object lock = new Object();
// /* package */final ArrayList<Item> mInternalItemList;
private final Rect mRect = new Rect();
/* package */Item mFocusedItem;
/* package */boolean mUpdate;
private int mSize;
// pre-projected points to zoomlovel 20
private static final byte MAX_ZOOM = 20;
class ItemOverlay extends RenderOverlay {
private SymbolLayer mSymbolLayer;
private float[] mMvp = new float[16];
private float[] mVec = new float[4];
public ItemOverlay(MapView mapView) {
super(mapView);
mSymbolLayer = new SymbolLayer();
}
// note: this is called from GL-Thread. so check your syncs!
@Override
public synchronized void update(MapPosition curPos, boolean positionChanged,
boolean tilesChanged) {
if (!tilesChanged && !mUpdate)
return;
mUpdate = false;
int diff = MAX_ZOOM - curPos.zoomLevel;
int mx = (int) curPos.x;
int my = (int) curPos.y;
// TODO could pass mvp as param
mMapView.getMapViewPosition().getMVP(mMvp);
float[] matrix = mMvp;
float[] vec = mVec;
// limit could be 1 if we update on every position change
float limit = 1.5f;
// no need to project these
int max = (1 << 11);
int changesInvisible = 0;
int changedVisible = 0;
int numVisible = 0;
synchronized (lock) {
// check changes
for (InternalItem it = mItems; it != null; it = it.next) {
it.x = (it.px >> diff) - mx;
it.y = (it.py >> diff) - my;
if (it.x > max || it.x < -max || it.y > max || it.y < -max) {
if (it.visible) {
it.changes = true;
changesInvisible++;
}
continue;
}
// map points to screen
vec[0] = it.x;
vec[1] = it.y;
vec[2] = 0;
vec[3] = 1;
Matrix.multiplyMV(vec, 0, matrix, 0, vec, 0);
float sx = vec[0] / vec[3];
float sy = vec[1] / vec[3];
// check if it is visible
if (sx < -limit || sx > limit || sy < -limit || sy > limit) {
// Log.d("..", "outside " + it.x + " " + it.y + " -> " + sx + " " + sy);
if (it.visible) {
it.changes = true;
changesInvisible++;
}
} else {
if (!it.visible) {
it.visible = true;
changedVisible++;
}
it.changes = false;
numVisible++;
}
}
// only update when zoomlevel changed, new items are visible
// or more than 10 of the current items became invisible
if (((curPos.zoomLevel == mMapPosition.zoomLevel || numVisible == 0)) &&
(changedVisible == 0 && changesInvisible < 10))
return;
// keep position for current state
// updateMapPosition();
// TODO add copy utility function
mMapPosition.x = curPos.x;
mMapPosition.y = curPos.y;
mMapPosition.zoomLevel = curPos.zoomLevel;
mMapPosition.scale = curPos.scale;
mMapPosition.angle = curPos.angle;
// items are placed relative to scale == 1
mMapPosition.scale = 1;
layers.clear();
for (InternalItem it = mItems; it != null; it = it.next) {
if (!it.visible)
continue;
if (it.changes) {
it.visible = false;
continue;
}
Item item = it.item; //mInternalItemList.get(i);
int state = 0;
if (mDrawFocusedItem && (mFocusedItem == item))
state = OverlayItem.ITEM_STATE_FOCUSED_MASK;
Drawable marker = item.getDrawable();
if (marker == null)
marker = mDefaultMarker;
// if (item.getMarker(state) == null) {
// OverlayItem.setState(mDefaultMarker, state);
// marker = mDefaultMarker;
// } else
// marker = item.getMarker(state);
boundToHotspot(marker, item.getMarkerHotspot());
mSymbolLayer.addDrawable(marker, state, it.x, it.y);
}
}
// Log.d("...", "changed " + changedVisible + " " + changesInvisible);
mSymbolLayer.prepare();
layers.textureLayers = mSymbolLayer;
newData = true;
}
}
/**
* Method by which subclasses create the actual Items. This will only be
* called from populate() we'll cache them for later use.
* @param i
* ...
* @return ...
*/
protected abstract Item createItem(int i);
/**
* The number of items in this overlay.
* @return ...
*/
public abstract int size();
public ItemizedOverlay(MapView mapView, final Drawable pDefaultMarker, final ResourceProxy
pResourceProxy) {
super(pResourceProxy);
if (pDefaultMarker == null) {
throw new IllegalArgumentException("You must pass a default marker to ItemizedOverlay.");
}
this.mDefaultMarker = pDefaultMarker;
// mInternalItemList = new ArrayList<Item>();
mMapView = mapView;
mLayer = new ItemOverlay(mapView);
}
/**
* Utility method to perform all processing on a new ItemizedOverlay.
* Subclasses provide Items through the createItem(int) method. The subclass
* should call this as soon as it has data, before anything else gets
* called.
*/
protected final void populate() {
synchronized (lock) {
final int size = size();
mSize = size;
// reuse previous items
InternalItem pool = mItems;
mItems = null;
// flip order to draw in backward cycle, so the items
// with the least index are on the front.
for (int a = 0; a < size; a++) {
InternalItem it;
if (pool != null) {
it = pool;
it.visible = false;
it.changes = false;
pool = pool.next;
} else {
it = new InternalItem();
}
it.next = mItems;
mItems = it;
it.item = createItem(a);
// pre-project points
GeoPoint p = it.item.mGeoPoint;
it.px = (int) MercatorProjection.longitudeToPixelX(p.getLongitude(), MAX_ZOOM);
it.py = (int) MercatorProjection.latitudeToPixelY(p.getLatitude(), MAX_ZOOM);
}
mUpdate = true;
}
}
/**
* Returns the Item at the given index.
* @param position
* the position of the item to return
* @return the Item of the given index.
*/
public final Item getItem(final int position) {
synchronized (lock) {
InternalItem item = mItems;
for (int i = mSize - position - 1; i > 0 && item != null; i--)
item = item.next;
if (item != null)
return item.item;
return null;
}
// return mInternalItemList.get(position);
}
// private Drawable getDefaultMarker(final int state) {
// OverlayItem.setState(mDefaultMarker, state);
// return mDefaultMarker;
// }
/**
* See if a given hit point is within the bounds of an item's marker.
* Override to modify the way an item is hit tested. The hit point is
* relative to the marker's bounds. The default implementation just checks
* to see if the hit point is within the touchable bounds of the marker.
* @param item
* the item to hit test
* @param marker
* the item's marker
* @param hitX
* x coordinate of point to check
* @param hitY
* y coordinate of point to check
* @return true if the hit point is within the marker
*/
protected boolean hitTest(final Item item, final android.graphics.drawable.Drawable marker,
final int hitX,
final int hitY) {
return marker.getBounds().contains(hitX, hitY);
}
/**
* Set whether or not to draw the focused item. The default is to draw it,
* but some clients may prefer to draw the focused item themselves.
* @param drawFocusedItem
* ...
*/
public void setDrawFocusedItem(final boolean drawFocusedItem) {
mDrawFocusedItem = drawFocusedItem;
}
/**
* If the given Item is found in the overlay, force it to be the current
* focus-bearer. Any registered {@@link
* ItemizedOverlay#OnFocusChangeListener} will be notified. This does not
* move the map, so if the Item isn't already centered, the user may get
* confused. If the Item is not found, this is a no-op. You can also pass
* null to remove focus.
* @param item
* ...
*/
public void setFocus(final Item item) {
mFocusedItem = item;
}
/**
* @return the currently-focused item, or null if no item is currently
* focused.
*/
public Item getFocus() {
return mFocusedItem;
}
/**
* Adjusts a drawable's bounds so that (0,0) is a pixel in the location
* described by the hotspot parameter. Useful for "pin"-like graphics. For
* convenience, returns the same drawable that was passed in.
* @param marker
* the drawable to adjust
* @param hotspot
* the hotspot for the drawable
* @return the same drawable that was passed in.
*/
protected synchronized Drawable boundToHotspot(final Drawable marker, HotspotPlace hotspot) {
final int markerWidth = marker.getIntrinsicWidth();
final int markerHeight = marker.getIntrinsicHeight();
mRect.set(0, 0, 0 + markerWidth, 0 + markerHeight);
if (hotspot == null) {
hotspot = HotspotPlace.BOTTOM_CENTER;
}
switch (hotspot) {
default:
case NONE:
break;
case CENTER:
mRect.offset(-markerWidth / 2, -markerHeight / 2);
break;
case BOTTOM_CENTER:
mRect.offset(-markerWidth / 2, -markerHeight);
break;
case TOP_CENTER:
mRect.offset(-markerWidth / 2, 0);
break;
case RIGHT_CENTER:
mRect.offset(-markerWidth, -markerHeight / 2);
break;
case LEFT_CENTER:
mRect.offset(0, -markerHeight / 2);
break;
case UPPER_RIGHT_CORNER:
mRect.offset(-markerWidth, 0);
break;
case LOWER_RIGHT_CORNER:
mRect.offset(-markerWidth, -markerHeight);
break;
case UPPER_LEFT_CORNER:
mRect.offset(0, 0);
break;
case LOWER_LEFT_CORNER:
mRect.offset(0, -markerHeight);
break;
}
marker.setBounds(mRect);
return marker;
}
// /**
// * Draw a marker on each of our items. populate() must have been called
// * first.<br/>
// * <br/>
// * The marker will be drawn twice for each Item in the Overlay--once in the
// * shadow phase, skewed and darkened, then again in the non-shadow phase.
// * The bottom-center of the marker will be aligned with the geographical
// * coordinates of the Item.<br/>
// * <br/>
// * The order of drawing may be changed by overriding the getIndexToDraw(int)
// * method. An item may provide an alternate marker via its
// * OverlayItem.getMarker(int) method. If that method returns null, the
// * default marker is used.<br/>
// * <br/>
// * The focused item is always drawn last, which puts it visually on top of
// * the other items.<br/>
// *
// * @param canvas
// * the Canvas upon which to draw. Note that this may already have
// * a transformation applied, so be sure to leave it the way you
// * found it
// * @param mapView
// * the MapView that requested the draw. Use
// * MapView.getProjection() to convert between on-screen pixels
// * and latitude/longitude pairs
// * @param shadow
// * if true, draw the shadow layer. If false, draw the overlay
// * contents.
// */
// @Override
// public void draw(final Canvas canvas, final MapView mapView, final boolean shadow) {
//
// if (shadow) {
// return;
// }
//
// final Projection pj = mapView.getProjection();
// final int size = this.mInternalItemList.size() - 1;
//
// /*
// * Draw in backward cycle, so the items with the least index are on the
// * front.
// */
// for (int i = size; i >= 0; i--) {
// final Item item = getItem(i);
// pj.toMapPixels(item.mGeoPoint, mCurScreenCoords);
//
// onDrawItem(canvas, item, mCurScreenCoords);
// }
// }
// /**
// * Draws an item located at the provided screen coordinates to the canvas.
// *
// * @param canvas
// * what the item is drawn upon
// * @param item
// * the item to be drawn
// * @param curScreenCoords
// * the screen coordinates of the item
// */
// protected void onDrawItem(final Canvas canvas, final Item item, final Point curScreenCoords) {
// int state = 0;
//
// if (mDrawFocusedItem && (mFocusedItem == item))
// state = OverlayItem.ITEM_STATE_FOCUSED_MASK;
//
// Drawable marker;
//
// if (item.getMarker(state) == null)
// marker = getDefaultMarker(state);
// else
// marker = item.getMarker(state);
//
// boundToHotspot(marker, item.getMarkerHotspot());
//
// // draw it
// Overlay.drawAt(canvas, marker, curScreenCoords.x, curScreenCoords.y, false);
// }
}

View File

@ -0,0 +1,33 @@
/*
* 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.overlay;
import org.oscim.renderer.overlays.OverlayText;
import org.oscim.view.MapView;
public class LabelingOverlay extends Overlay {
// private OverlayText mLayer;
// @Override
// public org.oscim.renderer.overlays.RenderOverlay getLayer() {
// return mLayer;
// }
public LabelingOverlay(MapView mapView) {
super();
mLayer = new OverlayText(mapView);
}
}

View File

@ -0,0 +1,36 @@
// Created by plusminus on 18:00:24 - 25.09.2008
package org.oscim.overlay;
/**
*
* This class contains constants used by the map view.
*
* @author Nicolas Gramlich
*
*/
public interface MapViewConstants {
// ===========================================================
// Final Fields
// ===========================================================
public static final boolean DEBUGMODE = false;
public static final int NOT_SET = Integer.MIN_VALUE;
public static final int ANIMATION_SMOOTHNESS_LOW = 4;
public static final int ANIMATION_SMOOTHNESS_DEFAULT = 10;
public static final int ANIMATION_SMOOTHNESS_HIGH = 20;
public static final int ANIMATION_DURATION_SHORT = 500;
public static final int ANIMATION_DURATION_DEFAULT = 1000;
public static final int ANIMATION_DURATION_LONG = 2000;
/** Minimum Zoom Level */
public static final int MINIMUM_ZOOMLEVEL = 0;
/**
* Maximum Zoom Level - we use Integers to store zoom levels so overflow happens at 2^32 - 1,
* but we also have a tile size that is typically 2^8, so (32-1)-8-1 = 22
*/
public static final int MAXIMUM_ZOOMLEVEL = 22;
}

View File

@ -0,0 +1,450 @@
// Created by plusminus on 20:32:01 - 27.09.2008
package org.oscim.overlay;
import java.util.concurrent.atomic.AtomicInteger;
import org.oscim.core.MapPosition;
import org.oscim.renderer.overlays.RenderOverlay;
import org.oscim.view.MapView;
import android.content.Context;
import android.graphics.Point;
import android.view.KeyEvent;
import android.view.MotionEvent;
/**
* Base class representing an overlay which may be displayed on top of a
* {@link MapView}. To add an overlay, subclass this class, create an instance,
* and add it to the list obtained from getOverlays() of {@link MapView}. This
* class implements a form of Gesture Handling similar to
* {@link android.view.GestureDetector.SimpleOnGestureListener} and
* GestureDetector.OnGestureListener. The difference is there is an additional
* argument for the item.
*
* @author Nicolas Gramlich
*/
public abstract class Overlay implements OverlayConstants {
// ===========================================================
// Constants
// ===========================================================
private static AtomicInteger sOrdinal = new AtomicInteger();
// From Google Maps API
protected static final float SHADOW_X_SKEW = -0.8999999761581421f;
protected static final float SHADOW_Y_SCALE = 0.5f;
// ===========================================================
// Fields
// ===========================================================
protected final ResourceProxy mResourceProxy;
protected final float mScale;
// private static final Rect mRect = new Rect();
private boolean mEnabled = true;
protected RenderOverlay mLayer;
public RenderOverlay getLayer() {
return mLayer;
}
// ===========================================================
// Constructors
// ===========================================================
public Overlay() {
mResourceProxy = null;
mScale = 1;
// mResourceProxy = new DefaultResourceProxyImpl(ctx);
// mScale = ctx.getResources().getDisplayMetrics().density;
}
public Overlay(final Context ctx) {
mResourceProxy = new DefaultResourceProxyImpl(ctx);
mScale = ctx.getResources().getDisplayMetrics().density;
}
public Overlay(final ResourceProxy pResourceProxy) {
mResourceProxy = pResourceProxy;
mScale = mResourceProxy.getDisplayMetricsDensity();
}
// ===========================================================
// Getter & Setter
// ===========================================================
/**
* Sets whether the Overlay is marked to be enabled. This setting does
* nothing by default, but should be checked before calling draw().
*
* @param pEnabled
* ...
*/
public void setEnabled(final boolean pEnabled) {
this.mEnabled = pEnabled;
}
/**
* Specifies if the Overlay is marked to be enabled. This should be checked
* before calling draw().
*
* @return true if the Overlay is marked enabled, false otherwise
*/
public boolean isEnabled() {
return this.mEnabled;
}
/**
* Since the menu-chain will pass through several independent Overlays, menu
* IDs cannot be fixed at compile time. Overlays should use this method to
* obtain and store a menu id for each menu item at construction time. This
* will ensure that two overlays don't use the same id.
*
* @return an integer suitable to be used as a menu identifier
*/
protected final static int getSafeMenuId() {
return sOrdinal.getAndIncrement();
}
/**
* Similar to <see cref="getSafeMenuId" />, except this reserves a sequence
* of IDs of length <param name="count" />. The returned number is the
* starting index of that sequential list.
*
* @param count
* ....
* @return an integer suitable to be used as a menu identifier
*/
protected final static int getSafeMenuIdSequence(final int count) {
return sOrdinal.getAndAdd(count);
}
// ===========================================================
// Methods for SuperClass/Interfaces
// ===========================================================
// /**
// * Draw the overlay over the map. This will be called on all active overlays
// * with shadow=true, to lay down the shadow layer, and then again on all
// * overlays with shadow=false. Callers should check isEnabled() before
// * calling draw(). By default, draws nothing.
// *
// * @param c
// * ...
// * @param osmv
// * ...
// * @param shadow
// * ...
// */
// protected abstract void draw(final Canvas c, final MapView osmv, final boolean shadow);
// ===========================================================
// Methods
// ===========================================================
/**
* Override to perform clean up of resources before shutdown. By default
* does nothing.
*
* @param mapView
* ...
*/
public void onDetach(final MapView mapView) {
}
/**
* By default does nothing (<code>return false</code>). If you handled the
* Event, return <code>true</code>, otherwise return <code>false</code>. If
* you returned <code>true</code> none of the following Overlays or the
* underlying {@link MapView} has the chance to handle this event.
*
* @param keyCode
* ...
* @param event
* ...
* @param mapView
* ...
* @return ...
*/
public boolean onKeyDown(final int keyCode, final KeyEvent event, final MapView mapView) {
return false;
}
/**
* By default does nothing (<code>return false</code>). If you handled the
* Event, return <code>true</code>, otherwise return <code>false</code>. If
* you returned <code>true</code> none of the following Overlays or the
* underlying {@link MapView} has the chance to handle this event.
*
* @param keyCode
* ...
* @param event
* ...
* @param mapView
* ...
* @return ...
*/
public boolean onKeyUp(final int keyCode, final KeyEvent event, final MapView mapView) {
return false;
}
/**
* <b>You can prevent all(!) other Touch-related events from happening!</b><br />
* By default does nothing (<code>return false</code>). If you handled the
* Event, return <code>true</code>, otherwise return <code>false</code>. If
* you returned <code>true</code> none of the following Overlays or the
* underlying {@link MapView} has the chance to handle this event.
*
* @param e
* ...
* @param mapView
* ...
* @return ...
*/
public boolean onTouchEvent(final MotionEvent e, final MapView mapView) {
return false;
}
/**
* By default does nothing (<code>return false</code>). If you handled the
* Event, return <code>true</code>, otherwise return <code>false</code>. If
* you returned <code>true</code> none of the following Overlays or the
* underlying {@link MapView} has the chance to handle this event.
*
* @param e
* ...
* @param mapView
* ...
* @return ...
*/
public boolean onTrackballEvent(final MotionEvent e, final MapView mapView) {
return false;
}
/** GestureDetector.OnDoubleTapListener **/
/**
* By default does nothing (<code>return false</code>). If you handled the
* Event, return <code>true</code>, otherwise return <code>false</code>. If
* you returned <code>true</code> none of the following Overlays or the
* underlying {@link MapView} has the chance to handle this event.
*
* @param e
* ...
* @param mapView
* ...
* @return ...
*/
public boolean onDoubleTap(final MotionEvent e, final MapView mapView) {
return false;
}
/**
* By default does nothing (<code>return false</code>). If you handled the
* Event, return <code>true</code>, otherwise return <code>false</code>. If
* you returned <code>true</code> none of the following Overlays or the
* underlying {@link MapView} has the chance to handle this event.
*
* @param e
* ...
* @param mapView
* ...
* @return ...
*/
public boolean onDoubleTapEvent(final MotionEvent e, final MapView mapView) {
return false;
}
/**
* By default does nothing (<code>return false</code>). If you handled the
* Event, return <code>true</code>, otherwise return <code>false</code>. If
* you returned <code>true</code> none of the following Overlays or the
* underlying {@link MapView} has the chance to handle this event.
*
* @param e
* ...
* @param mapView
* ...
* @return ...
*/
public boolean onSingleTapConfirmed(final MotionEvent e, final MapView mapView) {
return false;
}
/** OnGestureListener **/
/**
* By default does nothing (<code>return false</code>). If you handled the
* Event, return <code>true</code>, otherwise return <code>false</code>. If
* you returned <code>true</code> none of the following Overlays or the
* underlying {@link MapView} has the chance to handle this event.
*
* @param e
* ...
* @param mapView
* ...
* @return ...
*/
public boolean onDown(final MotionEvent e, final MapView mapView) {
return false;
}
/**
* By default does nothing (<code>return false</code>). If you handled the
* Event, return <code>true</code>, otherwise return <code>false</code>. If
* you returned <code>true</code> none of the following Overlays or the
* underlying {@link MapView} has the chance to handle this event.
*
* @param pEvent1
* ...
* @param pEvent2
* ...
* @param pVelocityX
* ...
* @param pVelocityY
* ...
* @param pMapView
* ...
* @return ...
*/
public boolean onFling(final MotionEvent pEvent1, final MotionEvent pEvent2,
final float pVelocityX, final float pVelocityY, final MapView pMapView) {
return false;
}
/**
* By default does nothing (<code>return false</code>). If you handled the
* Event, return <code>true</code>, otherwise return <code>false</code>. If
* you returned <code>true</code> none of the following Overlays or the
* underlying {@link MapView} has the chance to handle this event.
*
* @param e
* ...
* @param mapView
* ...
* @return ...
*/
public boolean onLongPress(final MotionEvent e, final MapView mapView) {
return false;
}
/**
* By default does nothing (<code>return false</code>). If you handled the
* Event, return <code>true</code>, otherwise return <code>false</code>. If
* you returned <code>true</code> none of the following Overlays or the
* underlying {@link MapView} has the chance to handle this event.
*
* @param pEvent1
* ...
* @param pEvent2
* ...
* @param pDistanceX
* ...
* @param pDistanceY
* ...
* @param pMapView
* ...
* @return ...
*/
public boolean onScroll(final MotionEvent pEvent1, final MotionEvent pEvent2,
final float pDistanceX, final float pDistanceY, final MapView pMapView) {
return false;
}
/**
* @param pEvent
* ...
* @param pMapView
* ...
*/
public void onShowPress(final MotionEvent pEvent, final MapView pMapView) {
return;
}
/**
* By default does nothing (<code>return false</code>). If you handled the
* Event, return <code>true</code>, otherwise return <code>false</code>. If
* you returned <code>true</code> none of the following Overlays or the
* underlying {@link MapView} has the chance to handle this event.
*
* @param e
* ...
* @param mapView
* ...
* @return ...
*/
public boolean onSingleTapUp(final MotionEvent e, final MapView mapView) {
return false;
}
/**
* @param mapPosition
* current MapPosition
*/
public void onUpdate(MapPosition mapPosition) {
}
// /**
// * Convenience method to draw a Drawable at an offset. x and y are pixel
// * coordinates. You can find appropriate coordinates from latitude/longitude
// * using the MapView.getProjection() method on the MapView passed to you in
// * draw(Canvas, MapView, boolean).
// *
// * @param canvas
// * ...
// * @param drawable
// * ...
// * @param x
// * ...
// * @param y
// * ...
// * @param shadow
// * If true, draw only the drawable's shadow. Otherwise, draw the
// * drawable itself.
// */
// protected synchronized static void drawAt(final android.graphics.Canvas canvas,
// final android.graphics.drawable.Drawable drawable, final int x, final int y,
// final boolean shadow) {
// drawable.copyBounds(mRect);
// drawable.setBounds(mRect.left + x, mRect.top + y, mRect.right + x, mRect.bottom + y);
// drawable.draw(canvas);
// drawable.setBounds(mRect);
// }
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
/**
* Interface definition for overlays that contain items that can be snapped
* to (for example, when the user invokes a zoom, this could be called
* allowing the user to snap the zoom to an interesting point.)
*/
public interface Snappable {
/**
* Checks to see if the given x and y are close enough to an item
* resulting in snapping the current action (e.g. zoom) to the item.
*
* @param x
* The x in screen coordinates.
* @param y
* The y in screen coordinates.
* @param snapPoint
* To be filled with the the interesting point (in screen
* coordinates) that is closest to the given x and y. Can be
* untouched if not snapping.
* @param mapView
* The {@link MapView} that is requesting the snap. Use
* MapView.getProjection() to convert between on-screen
* pixels and latitude/longitude pairs.
* @return Whether or not to snap to the interesting point.
*/
boolean onSnapToItem(int x, int y, Point snapPoint, MapView mapView);
}
}

View File

@ -0,0 +1,16 @@
package org.oscim.overlay;
/**
* This class contains constants used by the overlays.
*/
public interface OverlayConstants {
// ===========================================================
// Final Fields
// ===========================================================
public static final boolean DEBUGMODE = false;
public static final int NOT_SET = Integer.MIN_VALUE;
public static final int DEFAULT_ZOOMLEVEL_MINIMAP_DIFFERENCE = 3;
}

View File

@ -0,0 +1,157 @@
// Created by plusminus on 00:02:58 - 03.10.2008
package org.oscim.overlay;
import org.oscim.core.GeoPoint;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
/** Immutable class describing a GeoPoint with a Title and a Description.
* @author Nicolas Gramlich
* @author Theodore Hong
* @author Fred Eisele */
public class OverlayItem {
// ===========================================================
// Constants
// ===========================================================
public static final int ITEM_STATE_FOCUSED_MASK = 4;
public static final int ITEM_STATE_PRESSED_MASK = 1;
public static final int ITEM_STATE_SELECTED_MASK = 2;
protected static final Point DEFAULT_MARKER_SIZE = new Point(26, 94);
/** Indicates a hotspot for an area. This is where the origin (0,0) of a
* point will be located relative to the area. In otherwords this acts as an
* offset. NONE indicates that no adjustment should be made. */
public enum HotspotPlace {
NONE, CENTER, BOTTOM_CENTER, TOP_CENTER, RIGHT_CENTER, LEFT_CENTER, UPPER_RIGHT_CORNER, LOWER_RIGHT_CORNER, UPPER_LEFT_CORNER, LOWER_LEFT_CORNER
}
// ===========================================================
// Fields
// ===========================================================
public final String mUid;
public final String mTitle;
public final String mDescription;
public final GeoPoint mGeoPoint;
protected Drawable mMarker;
protected HotspotPlace mHotspotPlace;
// ===========================================================
// Constructors
// ===========================================================
/** @param aTitle
* this should be <b>singleLine</b> (no <code>'\n'</code> )
* @param aDescription
* a <b>multiLine</b> description ( <code>'\n'</code> possible)
* @param aGeoPoint
* ... */
public OverlayItem(final String aTitle, final String aDescription, final GeoPoint aGeoPoint) {
this(null, aTitle, aDescription, aGeoPoint);
}
public OverlayItem(final String aUid, final String aTitle, final String aDescription,
final GeoPoint aGeoPoint) {
this.mTitle = aTitle;
this.mDescription = aDescription;
this.mGeoPoint = aGeoPoint;
this.mUid = aUid;
}
// ===========================================================
// Getter & Setter
// ===========================================================
public String getUid() {
return mUid;
}
public String getTitle() {
return mTitle;
}
public String getSnippet() {
return mDescription;
}
public GeoPoint getPoint() {
return mGeoPoint;
}
/* (copied from Google API docs) Returns the marker that should be used when
* drawing this item on the map. A null value means that the default marker
* should be drawn. Different markers can be returned for different states.
* The different markers can have different bounds. The default behavior is
* to call {@link setState(android.graphics.drawable.Drawable, int)} on the
* overlay item's marker, if it exists, and then return it.
* @param stateBitset The current state.
* @return The marker for the current state, or null if the default marker
* for the overlay should be used. */
public Drawable getMarker(final int stateBitset) {
// marker not specified
if (mMarker == null) {
return null;
}
// set marker state appropriately
setState(mMarker, stateBitset);
return mMarker;
}
public void setMarker(final Drawable marker) {
this.mMarker = marker;
}
public void setMarkerHotspot(final HotspotPlace place) {
this.mHotspotPlace = (place == null) ? HotspotPlace.BOTTOM_CENTER : place;
}
public HotspotPlace getMarkerHotspot() {
return this.mHotspotPlace;
}
// ===========================================================
// Methods from SuperClass/Interfaces
// ===========================================================
// ===========================================================
// Methods
// ===========================================================
/* (copied from the Google API docs) Sets the state of a drawable to match a
* given state bitset. This is done by converting the state bitset bits
* into
* a state set of R.attr.state_pressed, R.attr.state_selected and
* R.attr.state_focused attributes, and then calling {@link
* Drawable.setState(int[])}. */
public static void setState(final Drawable drawable, final int stateBitset) {
final int[] states = new int[3];
int index = 0;
if ((stateBitset & ITEM_STATE_PRESSED_MASK) > 0)
states[index++] = android.R.attr.state_pressed;
if ((stateBitset & ITEM_STATE_SELECTED_MASK) > 0)
states[index++] = android.R.attr.state_selected;
if ((stateBitset & ITEM_STATE_FOCUSED_MASK) > 0)
states[index++] = android.R.attr.state_focused;
drawable.setState(states);
}
public Drawable getDrawable() {
return this.mMarker;
}
public int getWidth() {
return this.mMarker.getIntrinsicWidth();
}
public int getHeight() {
return this.mMarker.getIntrinsicHeight();
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
}

View File

@ -0,0 +1,390 @@
package org.oscim.overlay;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.CopyOnWriteArrayList;
import org.oscim.core.MapPosition;
import org.oscim.overlay.Overlay.Snappable;
import org.oscim.renderer.overlays.RenderOverlay;
import org.oscim.view.MapView;
import android.graphics.Point;
import android.view.KeyEvent;
import android.view.MotionEvent;
public class OverlayManager extends AbstractList<Overlay> {
// private TilesOverlay mTilesOverlay;
/* package */final CopyOnWriteArrayList<Overlay> mOverlayList;
public OverlayManager() {
// final TilesOverlay tilesOverlay) {
// setTilesOverlay(tilesOverlay);
mOverlayList = new CopyOnWriteArrayList<Overlay>();
}
@Override
public Overlay get(final int pIndex) {
return mOverlayList.get(pIndex);
}
@Override
public int size() {
return mOverlayList.size();
}
@Override
public void add(final int pIndex, final Overlay pElement) {
mOverlayList.add(pIndex, pElement);
mUpdateLayers = true;
}
@Override
public Overlay remove(final int pIndex) {
mUpdateLayers = true;
return mOverlayList.remove(pIndex);
}
@Override
public Overlay set(final int pIndex, final Overlay pElement) {
mUpdateLayers = true;
return mOverlayList.set(pIndex, pElement);
}
// /**
// * Gets the optional TilesOverlay class.
// *
// * @return the tilesOverlay
// */
// public TilesOverlay getTilesOverlay() {
// return mTilesOverlay;
// }
//
// /**
// * Sets the optional TilesOverlay class. If set, this overlay will be
// drawn before all other
// * overlays and will not be included in the editable list of overlays and
// can't be cleared
// * except by a subsequent call to setTilesOverlay().
// *
// * @param tilesOverlay
// * the tilesOverlay to set
// */
// public void setTilesOverlay(final TilesOverlay tilesOverlay) {
// mTilesOverlay = tilesOverlay;
// }
public Iterable<Overlay> overlaysReversed() {
return new Iterable<Overlay>() {
@Override
public Iterator<Overlay> iterator() {
final ListIterator<Overlay> i = mOverlayList.listIterator(mOverlayList.size());
return new Iterator<Overlay>() {
@Override
public boolean hasNext() {
return i.hasPrevious();
}
@Override
public Overlay next() {
return i.previous();
}
@Override
public void remove() {
i.remove();
}
};
}
};
}
private boolean mUpdateLayers;
private List<RenderOverlay> mDrawLayers = new ArrayList<RenderOverlay>();
public List<RenderOverlay> getRenderLayers() {
if (mUpdateLayers) {
synchronized (this) {
mUpdateLayers = false;
mDrawLayers.clear();
for (Overlay o : mOverlayList) {
RenderOverlay l = o.getLayer();
if (l != null)
mDrawLayers.add(l);
}
}
}
return mDrawLayers;
}
// public void onDraw(final Canvas c, final MapView pMapView) {
// // if ((mTilesOverlay != null) && mTilesOverlay.isEnabled()) {
// // mTilesOverlay.draw(c, pMapView, true);
// // }
// //
// // if ((mTilesOverlay != null) && mTilesOverlay.isEnabled()) {
// // mTilesOverlay.draw(c, pMapView, false);
// // }
//
// for (final Overlay overlay : mOverlayList) {
// if (overlay.isEnabled()) {
// overlay.draw(c, pMapView, true);
// }
// }
//
// for (final Overlay overlay : mOverlayList) {
// if (overlay.isEnabled()) {
// overlay.draw(c, pMapView, false);
// }
// }
//
// }
public void onDetach(final MapView pMapView) {
// if (mTilesOverlay != null) {
// mTilesOverlay.onDetach(pMapView);
// }
for (final Overlay overlay : this.overlaysReversed()) {
overlay.onDetach(pMapView);
}
}
public boolean onKeyDown(final int keyCode, final KeyEvent event, final MapView pMapView) {
for (final Overlay overlay : this.overlaysReversed()) {
if (overlay.onKeyDown(keyCode, event, pMapView)) {
return true;
}
}
return false;
}
public boolean onKeyUp(final int keyCode, final KeyEvent event, final MapView pMapView) {
for (final Overlay overlay : this.overlaysReversed()) {
if (overlay.onKeyUp(keyCode, event, pMapView)) {
return true;
}
}
return false;
}
public boolean onTouchEvent(final MotionEvent event, final MapView pMapView) {
for (final Overlay overlay : this.overlaysReversed()) {
if (overlay.onTouchEvent(event, pMapView)) {
return true;
}
}
return false;
}
public boolean onTrackballEvent(final MotionEvent event, final MapView pMapView) {
for (final Overlay overlay : this.overlaysReversed()) {
if (overlay.onTrackballEvent(event, pMapView)) {
return true;
}
}
return false;
}
public boolean onSnapToItem(final int x, final int y, final Point snapPoint,
final MapView pMapView) {
for (final Overlay overlay : this.overlaysReversed()) {
if (overlay instanceof Snappable) {
if (((Snappable) overlay).onSnapToItem(x, y, snapPoint, pMapView)) {
return true;
}
}
}
return false;
}
/* GestureDetector.OnDoubleTapListener */
public boolean onDoubleTap(final MotionEvent e, final MapView pMapView) {
for (final Overlay overlay : this.overlaysReversed()) {
if (overlay.onDoubleTap(e, pMapView)) {
return true;
}
}
return false;
}
public boolean onDoubleTapEvent(final MotionEvent e, final MapView pMapView) {
for (final Overlay overlay : this.overlaysReversed()) {
if (overlay.onDoubleTapEvent(e, pMapView)) {
return true;
}
}
return false;
}
public boolean onSingleTapConfirmed(final MotionEvent e, final MapView pMapView) {
for (final Overlay overlay : this.overlaysReversed()) {
if (overlay.onSingleTapConfirmed(e, pMapView)) {
return true;
}
}
return false;
}
/* OnGestureListener */
public boolean onDown(final MotionEvent pEvent, final MapView pMapView) {
for (final Overlay overlay : this.overlaysReversed()) {
if (overlay.onDown(pEvent, pMapView)) {
return true;
}
}
return false;
}
public boolean onFling(final MotionEvent pEvent1, final MotionEvent pEvent2,
final float pVelocityX, final float pVelocityY, final MapView pMapView) {
for (final Overlay overlay : this.overlaysReversed()) {
if (overlay.onFling(pEvent1, pEvent2, pVelocityX, pVelocityY, pMapView)) {
return true;
}
}
return false;
}
public boolean onLongPress(final MotionEvent pEvent, final MapView pMapView) {
for (final Overlay overlay : this.overlaysReversed()) {
if (overlay.onLongPress(pEvent, pMapView)) {
return true;
}
}
return false;
}
public boolean onScroll(final MotionEvent pEvent1, final MotionEvent pEvent2,
final float pDistanceX, final float pDistanceY, final MapView pMapView) {
for (final Overlay overlay : this.overlaysReversed()) {
if (overlay.onScroll(pEvent1, pEvent2, pDistanceX, pDistanceY, pMapView)) {
return true;
}
}
return false;
}
public void onShowPress(final MotionEvent pEvent, final MapView pMapView) {
for (final Overlay overlay : this.overlaysReversed()) {
overlay.onShowPress(pEvent, pMapView);
}
}
public boolean onSingleTapUp(final MotionEvent pEvent, final MapView pMapView) {
for (final Overlay overlay : this.overlaysReversed()) {
if (overlay.onSingleTapUp(pEvent, pMapView)) {
return true;
}
}
return false;
}
public void onUpdate(MapPosition mapPosition) {
for (final Overlay overlay : this.overlaysReversed()) {
overlay.onUpdate(mapPosition);
}
}
// ** Options Menu **//
// public void setOptionsMenusEnabled(final boolean pEnabled) {
// for (final Overlay overlay : mOverlayList) {
// if ((overlay instanceof IOverlayMenuProvider)
// && ((IOverlayMenuProvider) overlay).isOptionsMenuEnabled()) {
// ((IOverlayMenuProvider) overlay).setOptionsMenuEnabled(pEnabled);
// }
// }
// }
//
// public boolean onCreateOptionsMenu(final Menu pMenu, final int
// menuIdOffset,
// final MapView mapView) {
// boolean result = true;
// for (final Overlay overlay : this.overlaysReversed()) {
// if ((overlay instanceof IOverlayMenuProvider)
// && ((IOverlayMenuProvider) overlay).isOptionsMenuEnabled()) {
// result &= ((IOverlayMenuProvider) overlay).onCreateOptionsMenu(pMenu,
// menuIdOffset,
// mapView);
// }
// }
//
// if ((mTilesOverlay != null) && (mTilesOverlay instanceof
// IOverlayMenuProvider)
// && ((IOverlayMenuProvider) mTilesOverlay).isOptionsMenuEnabled()) {
// result &= mTilesOverlay.onCreateOptionsMenu(pMenu, menuIdOffset,
// mapView);
// }
//
// return result;
// }
//
// public boolean onPrepareOptionsMenu(final Menu pMenu, final int
// menuIdOffset,
// final MapView mapView) {
// for (final Overlay overlay : this.overlaysReversed()) {
// if ((overlay instanceof IOverlayMenuProvider)
// && ((IOverlayMenuProvider) overlay).isOptionsMenuEnabled()) {
// ((IOverlayMenuProvider) overlay).onPrepareOptionsMenu(pMenu,
// menuIdOffset, mapView);
// }
// }
//
// if ((mTilesOverlay != null) && (mTilesOverlay instanceof
// IOverlayMenuProvider)
// && ((IOverlayMenuProvider) mTilesOverlay).isOptionsMenuEnabled()) {
// mTilesOverlay.onPrepareOptionsMenu(pMenu, menuIdOffset, mapView);
// }
//
// return true;
// }
//
// public boolean onOptionsItemSelected(final MenuItem item, final int
// menuIdOffset,
// final MapView mapView) {
// for (final Overlay overlay : this.overlaysReversed()) {
// if ((overlay instanceof IOverlayMenuProvider)
// && ((IOverlayMenuProvider) overlay).isOptionsMenuEnabled()
// && ((IOverlayMenuProvider) overlay).onOptionsItemSelected(item,
// menuIdOffset,
// mapView)) {
// return true;
// }
// }
//
// if ((mTilesOverlay != null)
// && (mTilesOverlay instanceof IOverlayMenuProvider)
// && ((IOverlayMenuProvider) mTilesOverlay).isOptionsMenuEnabled()
// && ((IOverlayMenuProvider) mTilesOverlay).onOptionsItemSelected(item,
// menuIdOffset,
// mapView)) {
// return true;
// }
//
// return false;
// }
}

View File

@ -0,0 +1,280 @@
/*
* Copyright 2012, osmdroid: Viesturs Zarins, Martin Pearman
* 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.overlay;
import java.util.ArrayList;
import org.oscim.core.GeoPoint;
import org.oscim.core.MapPosition;
import org.oscim.core.MercatorProjection;
import org.oscim.renderer.layer.Layer;
import org.oscim.renderer.layer.LineLayer;
import org.oscim.renderer.overlays.RenderOverlay;
import org.oscim.theme.renderinstruction.Line;
import org.oscim.view.MapView;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
/** This class draws a path line in given color. */
public class PathOverlay extends Overlay {
/** Stores points, converted to the map projection. */
/* package */final ArrayList<GeoPoint> mPoints;
/* package */boolean mUpdatePoints;
/** Paint settings. */
protected Paint mPaint = new Paint();
class RenderPath extends RenderOverlay {
private static final byte MAX_ZOOM = 20;
// pre-projected points to zoomlovel 20
private int[] mPreprojected;
// projected points
private float[] mPPoints;
private short[] mIndex;
private int mSize;
private Line mLine;
// limit coords
private final int max = 2048;
public RenderPath(MapView mapView) {
super(mapView);
mLine = new Line(Color.BLUE, 3.0f, Cap.BUTT);
mIndex = new short[1];
mPPoints = new float[1];
}
// note: this is called from GL-Thread. so check your syncs!
// TODO use an Overlay-Thread to build up layers (like for Labeling)
@Override
public synchronized void update(MapPosition curPos, boolean positionChanged,
boolean tilesChanged) {
if (!tilesChanged && !mUpdatePoints)
return;
float[] projected = mPPoints;
if (mUpdatePoints) {
// pre-project point on zoomlelvel 20
synchronized (mPoints) {
mUpdatePoints = false;
ArrayList<GeoPoint> geopoints = mPoints;
int size = geopoints.size();
int[] points = mPreprojected;
mSize = size * 2;
if (mSize > projected.length) {
points = mPreprojected = new int[mSize];
projected = mPPoints = new float[mSize];
}
for (int i = 0, j = 0; i < size; i++, j += 2) {
GeoPoint p = geopoints.get(i);
points[j + 0] = (int) MercatorProjection.longitudeToPixelX(
p.getLongitude(), MAX_ZOOM);
points[j + 1] = (int) MercatorProjection.latitudeToPixelY(
p.getLatitude(), MAX_ZOOM);
}
}
}
int size = mSize;
// keep position to render relative to current state
updateMapPosition();
// items are placed relative to scale == 1
mMapPosition.scale = 1;
// layers.clear();
LineLayer ll = (LineLayer) layers.getLayer(1, Layer.LINE);
// reset verticesCnt to reuse layer
ll.verticesCnt = 0;
ll.line = mLine;
ll.width = 2.5f;
int x, y, px = 0, py = 0;
int i = 0;
int diff = MAX_ZOOM - mMapPosition.zoomLevel;
int mx = (int) mMapPosition.x;
int my = (int) mMapPosition.y;
for (int j = 0; j < size; j += 2) {
// TODO translate mapPosition and do this after clipping
x = (mPreprojected[j + 0] >> diff) - mx;
y = (mPreprojected[j + 1] >> diff) - my;
// TODO use line clipping, this doesnt work with 'GreatCircle'
// TODO clip to view bounding box
if (x > max || x < -max || y > max || y < -max) {
if (i > 2) {
mIndex[0] = (short) i;
ll.addLine(projected, mIndex, false);
}
i = 0;
continue;
}
// skip too near points
int dx = x - px;
int dy = y - py;
if ((i == 0) || dx > 2 || dx < -2 || dy > 2 || dy < -2) {
projected[i + 0] = px = x;
projected[i + 1] = py = y;
i += 2;
}
}
mIndex[0] = (short) i;
ll.addLine(projected, mIndex, false);
newData = true;
}
}
public PathOverlay(MapView mapView, final int color, final Context ctx) {
super(ctx);
this.mPaint.setColor(color);
this.mPaint.setStrokeWidth(2.0f);
this.mPaint.setStyle(Paint.Style.STROKE);
this.mPoints = new ArrayList<GeoPoint>();
mLayer = new RenderPath(mapView);
}
public void setColor(final int color) {
this.mPaint.setColor(color);
}
public void setAlpha(final int a) {
this.mPaint.setAlpha(a);
}
public Paint getPaint() {
return mPaint;
}
public void setPaint(final Paint pPaint) {
if (pPaint == null) {
throw new IllegalArgumentException("pPaint argument cannot be null");
}
mPaint = pPaint;
}
/** Draw a great circle. Calculate a point for every 100km along the path.
* @param startPoint
* start point of the great circle
* @param endPoint
* end point of the great circle */
public void addGreatCircle(final GeoPoint startPoint, final GeoPoint endPoint) {
synchronized (mPoints) {
// get the great circle path length in meters
final int greatCircleLength = startPoint.distanceTo(endPoint);
// add one point for every 100kms of the great circle path
final int numberOfPoints = greatCircleLength / 100000;
addGreatCircle(startPoint, endPoint, numberOfPoints);
}
}
/** Draw a great circle.
* @param startPoint
* start point of the great circle
* @param endPoint
* end point of the great circle
* @param numberOfPoints
* number of points to calculate along the path */
public void addGreatCircle(final GeoPoint startPoint, final GeoPoint endPoint,
final int numberOfPoints) {
// adapted from page
// http://compastic.blogspot.co.uk/2011/07/how-to-draw-great-circle-on-map-in.html
// which was adapted from page http://maps.forum.nu/gm_flight_path.html
// convert to radians
final double lat1 = startPoint.getLatitude() * Math.PI / 180;
final double lon1 = startPoint.getLongitude() * Math.PI / 180;
final double lat2 = endPoint.getLatitude() * Math.PI / 180;
final double lon2 = endPoint.getLongitude() * Math.PI / 180;
final double d = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin((lat1 - lat2) / 2), 2)
+ Math.cos(lat1) * Math.cos(lat2)
* Math.pow(Math.sin((lon1 - lon2) / 2), 2)));
double bearing = Math.atan2(
Math.sin(lon1 - lon2) * Math.cos(lat2),
Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2)
* Math.cos(lon1 - lon2))
/ -(Math.PI / 180);
bearing = bearing < 0 ? 360 + bearing : bearing;
for (int i = 0, j = numberOfPoints + 1; i < j; i++) {
final double f = 1.0 / numberOfPoints * i;
final double A = Math.sin((1 - f) * d) / Math.sin(d);
final double B = Math.sin(f * d) / Math.sin(d);
final double x = A * Math.cos(lat1) * Math.cos(lon1) + B * Math.cos(lat2)
* Math.cos(lon2);
final double y = A * Math.cos(lat1) * Math.sin(lon1) + B * Math.cos(lat2)
* Math.sin(lon2);
final double z = A * Math.sin(lat1) + B * Math.sin(lat2);
final double latN = Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));
final double lonN = Math.atan2(y, x);
addPoint((int) (latN / (Math.PI / 180) * 1E6), (int) (lonN / (Math.PI / 180) * 1E6));
}
}
public void clearPath() {
synchronized (mPoints) {
mPoints.clear();
mUpdatePoints = true;
}
}
public void addPoint(final GeoPoint pt) {
synchronized (mPoints) {
this.mPoints.add(pt);
mUpdatePoints = true;
}
}
public void addPoint(final int latitudeE6, final int longitudeE6) {
synchronized (mPoints) {
this.mPoints.add(new GeoPoint(latitudeE6, longitudeE6));
mUpdatePoints = true;
}
}
public int getNumberOfPoints() {
return this.mPoints.size();
}
}

View File

@ -0,0 +1,67 @@
package org.oscim.overlay;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
public interface ResourceProxy {
public static enum string {
// tile sources
mapnik, cyclemap, public_transport, base, topo, hills, cloudmade_small, cloudmade_standard, mapquest_osm, mapquest_aerial, bing,
// overlays
fiets_nl, base_nl, roads_nl,
// other stuff
unknown, format_distance_meters, format_distance_kilometers, format_distance_miles, format_distance_nautical_miles, format_distance_feet, online_mode, offline_mode, my_location, compass, map_mode,
}
public static enum bitmap {
/**
* For testing - the image doesn't exist.
*/
unknown,
center, direction_arrow, marker_default, marker_default_focused_base, navto_small, next, previous, person,
/**
* Menu icons
*/
ic_menu_offline, ic_menu_mylocation, ic_menu_compass, ic_menu_mapmode
}
String getString(string pResId);
/**
* Use a string resource as a format definition, and format using the
* supplied format arguments.
*
* @param pResId
* ...
* @param formatArgs
* ...
* @return ...
*/
String getString(string pResId, Object... formatArgs);
Bitmap getBitmap(bitmap pResId);
/**
* Get a bitmap as a {@link Drawable}
*
* @param pResId
* ...
* @return ...
*/
Drawable getDrawable(bitmap pResId);
/**
* Gets the density from the current screen's DisplayMetrics
*
* @return the screen's density
*/
float getDisplayMetricsDensity();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

View File

@ -25,7 +25,7 @@ import static android.opengl.GLES20.GL_POLYGON_OFFSET_FILL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import javax.microedition.khronos.egl.EGLConfig;
@ -35,8 +35,7 @@ import org.oscim.core.MapPosition;
import org.oscim.core.Tile;
import org.oscim.renderer.layer.Layer;
import org.oscim.renderer.layer.Layers;
import org.oscim.renderer.overlays.Overlay;
import org.oscim.renderer.overlays.OverlayText;
import org.oscim.renderer.overlays.RenderOverlay;
import org.oscim.theme.RenderTheme;
import org.oscim.utils.GlUtils;
import org.oscim.view.MapView;
@ -61,7 +60,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
static int CACHE_TILES = CACHE_TILES_MAX;
private final MapView mMapView;
private static MapView mMapView;
static int mWidth, mHeight;
private static MapViewPosition mMapViewPosition;
@ -177,10 +176,8 @@ public class GLRenderer implements GLSurfaceView.Renderer {
}
};
/**
* @param mapView
* the MapView
*/
/** @param mapView
* the MapView */
public GLRenderer(MapView mapView) {
mMapView = mapView;
@ -213,15 +210,15 @@ public class GLRenderer implements GLSurfaceView.Renderer {
shortBuffer[i].put(mFillCoords, 0, 8);
}
mOverlays = new ArrayList<Overlay>();
// overlays = new ArrayList<RenderOverlay>();
// mOverlays.add(new OverlayGrid(mapView));
// mOverlays.add(new OverlayTest(mapView));
mOverlays.add(new OverlayText(mapView));
// overlays.add(new OverlayText(mapView));
}
private static ArrayList<Overlay> mOverlays;
// private static ArrayList<RenderOverlay> overlays;
public static void setRenderTheme(RenderTheme t) {
mClearColor = GlUtils.colorToFloat(t.getMapBackground());
@ -234,7 +231,8 @@ public class GLRenderer implements GLSurfaceView.Renderer {
int newSize = layers.getSize();
if (newSize == 0) {
Log.d(TAG, "empty");
// FIXME why are there so many tiles empty?
// Log.d(TAG, "empty");
return true;
}
@ -315,14 +313,14 @@ public class GLRenderer implements GLSurfaceView.Renderer {
return tile.isReady;
}
private static boolean uploadOverlayData(Overlay overlay) {
private static boolean uploadOverlayData(RenderOverlay renderOverlay) {
if (uploadLayers(overlay.layers, overlay.vbo, true))
overlay.isReady = true;
if (uploadLayers(renderOverlay.layers, renderOverlay.vbo, true))
renderOverlay.isReady = true;
overlay.newData = false;
renderOverlay.newData = false;
return overlay.isReady;
return renderOverlay.isReady;
}
private static void checkBufferUsage() {
@ -384,7 +382,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
public void onDrawFrame(GL10 glUnused) {
// prevent main thread recreating all tiles (updateMap)
// while rendering is going.
// while rendering is going on.
drawlock.lock();
try {
draw();
@ -421,9 +419,11 @@ public class GLRenderer implements GLSurfaceView.Renderer {
// get current tiles to draw
mDrawTiles = TileManager.getActiveTiles(mDrawTiles);
// FIXME what if only drawing overlays?
if (mDrawTiles == null || mDrawTiles.cnt == 0) {
return;
}
boolean tilesChanged = false;
// check if the tiles have changed...
if (serial != mDrawTiles.serial) {
@ -450,14 +450,13 @@ public class GLRenderer implements GLSurfaceView.Renderer {
float div = scaleDiv(tiles[0]);
// transform screen coordinates to tile coordinates
float s = Tile.TILE_SIZE;
float scale = mapPosition.scale / div;
float px = (float) mapPosition.x * div;
float py = (float) mapPosition.y * div;
for (int i = 0; i < 8; i += 2) {
coords[i + 0] = (px + coords[i + 0] / scale) / s;
coords[i + 1] = (py + coords[i + 1] / scale) / s;
coords[i + 0] = (px + coords[i + 0] / scale) / Tile.TILE_SIZE;
coords[i + 1] = (py + coords[i + 1] / scale) / Tile.TILE_SIZE;
}
mHolderCount = 0;
@ -514,11 +513,11 @@ public class GLRenderer implements GLSurfaceView.Renderer {
tilesChanged |= (uploadCnt > 0);
// if (changed || tilesChanged) {
for (Overlay overlay : mOverlays) {
overlay.update(changed, tilesChanged);
}
// }
// update overlays
List<RenderOverlay> overlays = mMapView.getOverlayManager().getRenderLayers();
for (int i = 0, n = overlays.size(); i < n; i++)
overlays.get(i).update(mMapPosition, changed, tilesChanged);
GLES20.glEnable(GL_DEPTH_TEST);
GLES20.glEnable(GL_POLYGON_OFFSET_FILL);
@ -538,7 +537,6 @@ public class GLRenderer implements GLSurfaceView.Renderer {
if (t.isVisible && !t.isReady && (t.holder == null))
drawProxyTile(t);
}
// GlUtils.checkGlError("end draw");
GLES20.glDisable(GL_POLYGON_OFFSET_FILL);
GLES20.glDisable(GL_DEPTH_TEST);
@ -549,22 +547,25 @@ public class GLRenderer implements GLSurfaceView.Renderer {
GLES20.glEnable(GL_BLEND);
// call overlay renderer
for (Overlay overlay : mOverlays) {
if (overlay.newData) {
if (overlay.vbo == null)
overlay.vbo = BufferObject.get();
for (int i = 0, n = overlays.size(); i < n; i++) {
RenderOverlay renderOverlay = overlays.get(i);
if (overlay.vbo == null)
continue;
if (renderOverlay.newData) {
if (renderOverlay.vbo == null) {
renderOverlay.vbo = BufferObject.get();
if (uploadOverlayData(overlay))
overlay.isReady = true;
if (renderOverlay.vbo == null)
continue;
}
if (uploadOverlayData(renderOverlay))
renderOverlay.isReady = true;
}
if (!overlay.isReady)
continue;
// setMatrix(mMVPMatrix, overlay);
overlay.render(mMapPosition, mMVPMatrix, mProjMatrix);
if (renderOverlay.isReady) {
// setMatrix(mMVPMatrix, overlay);
renderOverlay.render(mMapPosition, mMVPMatrix, mProjMatrix);
}
}
if (MapView.debugFrameTime) {
@ -594,6 +595,9 @@ public class GLRenderer implements GLSurfaceView.Renderer {
mapPosition.viewMatrix, 0);
PolygonRenderer.debugDraw(mMVPMatrix, mDebugCoords, 1);
}
// mMapView.getOverlayManager().onUpdate(mMapPosition);
}
// used to not draw a tile twice per frame.

View File

@ -32,7 +32,7 @@ public class GLView extends GLSurfaceView {
setEGLConfigChooser(new GlConfigChooser());
setEGLContextClientVersion(2);
// setDebugFlags(DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS);
setDebugFlags(DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS);
mRenderer = new GLRenderer(mMapView);
setRenderer(mRenderer);

View File

@ -44,6 +44,7 @@ class QuadTree {
static boolean remove(MapTile t) {
if (t.rel == null) {
// Bad Things(tm) happened
Log.d(TAG, "already removed " + t);
return true;
}

View File

@ -13,12 +13,9 @@
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* ported from Polymaps: Layer.js Copyright (c) 2010, SimpleGeo and Stamen Design */
package org.oscim.renderer;
import android.util.FloatMath;
import android.util.Log;
public abstract class ScanBox {
@ -31,16 +28,14 @@ public abstract class ScanBox {
this.y0 = y0;
this.x1 = x1;
this.y1 = y1;
dx = x1 - x0;
dy = y1 - y0;
} else {
this.x0 = x1;
this.y0 = y1;
this.x1 = x0;
this.y1 = y0;
dx = x0 - x1;
dy = y0 - y1;
}
this.dx = this.x1 - this.x0;
this.dy = this.y1 - this.y0;
}
}
@ -49,33 +44,32 @@ public abstract class ScanBox {
private Edge ca = new Edge();
protected byte mZoom;
void scan(float[] coords, byte zoom) {
abstract void setVisible(int y, int x1, int x2);
public void scan(float[] coords, byte zoom) {
mZoom = zoom;
// top-left -> top-right
ab.set(coords[0], coords[1], coords[2], coords[3]);
// top-right -> bottom-right
bc.set(coords[2], coords[3], coords[4], coords[5]);
// bottom-right -> bottom-left
ca.set(coords[4], coords[5], coords[0], coords[1]);
scanTriangle();
ab.set(coords[4], coords[5], coords[6], coords[7]);
bc.set(coords[6], coords[7], coords[0], coords[1]);
ca.set(coords[0], coords[1], coords[4], coords[5]);
scanTriangle();
}
// top-left -> bottom-right
ab.set(coords[0], coords[1], coords[4], coords[5]);
// bottom-right -> bottom-left
bc.set(coords[4], coords[5], coords[6], coords[7]);
// bottom-left -> top-left
ca.set(coords[6], coords[7], coords[0], coords[1]);
/**
* @param y
* ...
* @param x1
* ...
* @param x2
* ...
*/
void setVisible(int y, int x1, int x2) {
scanTriangle();
}
private void scanTriangle() {
// sort so that ca.dy > bc.dy > ab.dy
if (ab.dy > bc.dy) {
Edge t = ab;
ab = bc;
@ -91,23 +85,21 @@ public abstract class ScanBox {
bc = ca;
ca = t;
}
// ca.dy > bc.dy > ab.dy
// shouldnt be possible, anyway
if (ca.dy == 0)
return;
if (ab.dy != 0)
if (ab.dy > 0.1)
scanSpans(ca, ab);
if (bc.dy != 0)
if (bc.dy > 0.1)
scanSpans(ca, bc);
}
// FIXME
private static final int MAX_SLOPE = 4;
private void scanSpans(Edge e0, Edge e1) {
// scan the y-range of the edge with less dy
int y0 = (int) Math.max(0, FloatMath.floor(e1.y0));
int y1 = (int) Math.min((1 << mZoom), FloatMath.ceil(e1.y1));
@ -131,41 +123,99 @@ public abstract class ScanBox {
float m0 = e0.dx / e0.dy;
float m1 = e1.dx / e1.dy;
// still needed?
if (m0 > MAX_SLOPE)
m0 = MAX_SLOPE;
else if (m0 < -MAX_SLOPE)
m0 = -MAX_SLOPE;
// FIXME, something is wrong here, with a steep angle
// 'fill' can shoot far over the area in one direction
int maxSlope = 8;
if (m0 > maxSlope)
m0 = maxSlope;
else if (m0 < -maxSlope)
m0 = -maxSlope;
if (m1 > MAX_SLOPE)
m1 = MAX_SLOPE;
else if (m1 < -MAX_SLOPE)
m1 = -MAX_SLOPE;
if (m1 > maxSlope)
m1 = maxSlope;
else if (m1 < -maxSlope)
m1 = -maxSlope;
// e0 goes to the right, e1 to the left
int d0 = e0.dx > 0 ? 1 : 0; // use y + 1 to compute x0
int d1 = e1.dx < 0 ? 1 : 0; // use y + 1 to compute x1
float x0, x1, dy;
float dy;
for (int y = y0; y < y1; y++) {
dy = y + d0 - e0.y0;
if (e0.dy < dy)
dy = d0 + y - e0.y0;
if (dy > e0.dy)
dy = e0.dy;
x0 = e0.x0 + m0 * dy;
x0 = FloatMath.ceil(x0);
float x0 = FloatMath.ceil(e0.x0 + m0 * dy);
dy = y + d1 - e1.y0;
if (e1.dy < dy)
dy = d1 + y - e1.y0;
if (dy > e1.dy)
dy = e1.dy;
x1 = e1.x0 + m1 * dy;
x1 = FloatMath.floor(x1);
float x1 = FloatMath.floor(e1.x0 + m1 * dy);
if (x1 > x0)
Log.d("...", "X set visible" + y + " " + x1 + "/" + x0);
setVisible(y, (int) x1, (int) x0);
if (x1 < x0)
setVisible(y, (int) x1, (int) x0);
}
}
/*
* ported from Polymaps: Layer.js Copyright (c) 2010, SimpleGeo and Stamen
* Design
*/
// // scan-line conversion
// function edge(a, b) {
// if (a.row > b.row) { var t = a; a = b; b = t; }
// return {
// x0: a.column,
// y0: a.row,
// x1: b.column,
// y1: b.row,
// dx: b.column - a.column,
// dy: b.row - a.row
// };
// }
//
// // scan-line conversion
// function scanSpans(e0, e1, ymin, ymax, scanLine) {
// var y0 = Math.max(ymin, Math.floor(e1.y0)),
// y1 = Math.min(ymax, Math.ceil(e1.y1));
//
// // sort edges by x-coordinate
// if ((e0.x0 == e1.x0 && e0.y0 == e1.y0)
// ? (e0.x0 + e1.dy / e0.dy * e0.dx < e1.x1)
// : (e0.x1 - e1.dy / e0.dy * e0.dx < e1.x0)) {
// var t = e0; e0 = e1; e1 = t;
// }
//
// // scan lines!
// var m0 = e0.dx / e0.dy,
// m1 = e1.dx / e1.dy,
// d0 = e0.dx > 0, // use y + 1 to compute x0
// d1 = e1.dx < 0; // use y + 1 to compute x1
// for (var y = y0; y < y1; y++) {
// var x0 = m0 * Math.max(0, Math.min(e0.dy, y + d0 - e0.y0)) + e0.x0,
// x1 = m1 * Math.max(0, Math.min(e1.dy, y + d1 - e1.y0)) + e1.x0;
// scanLine(Math.floor(x1), Math.ceil(x0), y);
// }
// }
//
// // scan-line conversion
// function scanTriangle(a, b, c, ymin, ymax, scanLine) {
// var ab = edge(a, b),
// bc = edge(b, c),
// ca = edge(c, a);
//
// // sort edges by y-length
// if (ab.dy > bc.dy) { var t = ab; ab = bc; bc = t; }
// if (ab.dy > ca.dy) { var t = ab; ab = ca; ca = t; }
// if (bc.dy > ca.dy) { var t = bc; bc = ca; ca = t; }
//
// // scan span! scan span!
// if (ab.dy) scanSpans(ca, ab, ymin, ymax, scanLine);
// if (bc.dy) scanSpans(ca, bc, ymin, ymax, scanLine);
// }
}

View File

@ -14,8 +14,9 @@
*/
package org.oscim.renderer;
import java.util.ArrayList;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.opengl.GLES20;
import android.opengl.GLUtils;
@ -24,11 +25,12 @@ import android.util.Log;
public class TextureObject {
private static TextureObject pool;
private static ArrayList<Bitmap> mBitmaps;
// shared bitmap and canvas for default texture size
public final static int TEXTURE_WIDTH = 256;
public final static int TEXTURE_HEIGHT = 256;
private static Bitmap[] mBitmap;
private static Canvas[] mCanvas;
private static int mBitmapFormat;
private static int mBitmapType;
private static int objectCount = 10;
@ -37,22 +39,38 @@ public class TextureObject {
TextureObject to;
if (pool == null) {
init(10);
objectCount += 10;
Log.d("...", "textures: " + objectCount);
objectCount += 1;
if (TextureRenderer.debug)
Log.d("...", "textures: " + objectCount);
pool = new TextureObject(-1);
}
to = pool;
pool = pool.next;
to.next = null;
to.bitmap = getBitmap();
to.bitmap.eraseColor(Color.TRANSPARENT);
if (TextureRenderer.debug)
Log.d("...", "get texture " + to.id + " " + to.bitmap);
return to;
}
public static synchronized void release(TextureObject to) {
while (to != null) {
if (TextureRenderer.debug)
Log.d("...", "release texture " + to.id);
TextureObject next = to.next;
if (to.bitmap != null) {
mBitmaps.add(to.bitmap);
to.bitmap = null;
}
to.next = pool;
pool = to;
@ -60,6 +78,26 @@ public class TextureObject {
}
}
public static synchronized void uploadTexture(TextureObject to) {
if (TextureRenderer.debug)
Log.d("...", "upload texture " + to.id);
if (to.id < 0) {
int[] textureIds = new int[1];
GLES20.glGenTextures(1, textureIds, 0);
to.id = textureIds[0];
initTexture(to.id);
if (TextureRenderer.debug)
Log.d("...", "new texture " + to.id);
}
uploadTexture(to, to.bitmap, mBitmapFormat, mBitmapType,
TEXTURE_WIDTH, TEXTURE_HEIGHT);
mBitmaps.add(to.bitmap);
to.bitmap = null;
}
public static void uploadTexture(TextureObject to, Bitmap bitmap,
int format, int type, int w, int h) {
@ -77,73 +115,74 @@ public class TextureObject {
}
}
static void initTexture(int id) {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE); // Set U Wrapping
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE); // Set V Wrapping
}
static void init(int num) {
pool = null;
TextureObject to;
int[] textureIds = new int[num];
GLES20.glGenTextures(num, textureIds, 0);
for (int i = 1; i < num; i++) {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[i]);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE); // Set U Wrapping
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE); // Set V Wrapping
initTexture(textureIds[i]);
to = new TextureObject(textureIds[i]);
to.next = pool;
pool = to;
}
mBitmap = new Bitmap[4];
mCanvas = new Canvas[4];
mBitmaps = new ArrayList<Bitmap>(10);
for (int i = 0; i < 4; i++) {
mBitmap[i] = Bitmap.createBitmap(TEXTURE_WIDTH, TEXTURE_HEIGHT,
Bitmap bitmap = Bitmap.createBitmap(TEXTURE_WIDTH, TEXTURE_HEIGHT,
Bitmap.Config.ARGB_8888);
mCanvas[i] = new Canvas(mBitmap[i]);
mBitmaps.add(bitmap);
}
mBitmapFormat = GLUtils.getInternalFormat(mBitmap[0]);
mBitmapType = GLUtils.getType(mBitmap[0]);
mBitmapFormat = GLUtils.getInternalFormat(mBitmaps.get(0));
mBitmapType = GLUtils.getType(mBitmaps.get(0));
}
private static int curCanvas = 0;
private static Bitmap getBitmap() {
int size = mBitmaps.size();
if (size == 0) {
for (int i = 0; i < 4; i++) {
Bitmap bitmap = Bitmap.createBitmap(TEXTURE_WIDTH, TEXTURE_HEIGHT,
Bitmap.Config.ARGB_8888);
public static Canvas getCanvas() {
curCanvas = ++curCanvas % 4;
mBitmap[curCanvas].eraseColor(Color.TRANSPARENT);
return mCanvas[curCanvas];
}
public static TextureObject uploadCanvas(short offset, short indices) {
TextureObject to = get();
uploadTexture(to, mBitmap[curCanvas],
mBitmapFormat, mBitmapType,
TEXTURE_WIDTH, TEXTURE_HEIGHT);
to.offset = offset;
to.vertices = (short) (indices - offset);
return to;
mBitmaps.add(bitmap);
}
size = 4;
}
return mBitmaps.remove(size - 1);
}
public TextureObject next;
public Bitmap bitmap;
int id;
int width;
int height;
// vertex offset from which this texture is referenced
// or store texture id with vertex?
short offset;
short vertices;
public short offset;
public short vertices;
TextureObject(int id) {
this.id = id;

View File

@ -24,8 +24,11 @@ import org.oscim.renderer.layer.TextureLayer;
import org.oscim.utils.GlUtils;
import android.opengl.GLES20;
import android.util.Log;
public final class TextureRenderer {
public final static boolean debug = false;
private static int mTextureProgram;
private static int hTextureMVMatrix;
private static int hTextureProjMatrix;
@ -35,7 +38,7 @@ public final class TextureRenderer {
private static int hTextureTexCoord;
private static int mIndicesVBO;
final static int INDICES_PER_SPRITE = 6;
public final static int INDICES_PER_SPRITE = 6;
final static int VERTICES_PER_SPRITE = 4;
final static int SHORTS_PER_VERTICE = 6;
// per texture
@ -89,9 +92,9 @@ public final class TextureRenderer {
public static Layer draw(Layer layer, float scale, float[] projection,
float matrix[], int offset) {
GlUtils.checkGlError("draw texture0");
// GlUtils.checkGlError("draw texture >");
GLES20.glUseProgram(mTextureProgram);
GlUtils.checkGlError("draw texture1");
int va = hTextureTexCoord;
if (!GLRenderer.vertexArray[va]) {
@ -106,39 +109,48 @@ public final class TextureRenderer {
}
TextureLayer tl = (TextureLayer) layer;
GlUtils.checkGlError("draw texture2.");
GLES20.glUniform1f(hTextureScale, scale);
if (tl.fixed)
GLES20.glUniform1f(hTextureScale, scale);
else
GLES20.glUniform1f(hTextureScale, 1);
GLES20.glUniform1f(hTextureScreenScale, 1f / GLRenderer.mWidth);
GLES20.glUniformMatrix4fv(hTextureProjMatrix, 1, false, projection, 0);
GLES20.glUniformMatrix4fv(hTextureMVMatrix, 1, false, matrix, 0);
GlUtils.checkGlError("draw texture2");
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, mIndicesVBO);
GlUtils.checkGlError("draw texture3");
for (TextureObject to = tl.textures; to != null; to = to.next) {
if (TextureRenderer.debug)
Log.d("...", "draw texture: " + to.id + " " + to.offset + " " + to.vertices);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, to.id);
GlUtils.checkGlError("draw texture4");
int maxVertices = MAX_ITEMS * INDICES_PER_SPRITE;
GlUtils.checkGlError("draw texture5");
for (int i = 0; i < to.vertices; i += maxVertices) {
// to.offset * (24(shorts) * 2(short-bytes) / 6(indices) == 8)
int off = (to.offset + i) * 8 + offset;
// to.offset * 24(shorts) * 2(short-bytes) / 6(indices)
GLES20.glVertexAttribPointer(hTextureVertex, 4,
GLES20.GL_SHORT, false, 12, to.offset * 8 + offset);
GlUtils.checkGlError("draw texture..");
GLES20.glVertexAttribPointer(hTextureVertex, 4,
GLES20.GL_SHORT, false, 12, off);
GLES20.glVertexAttribPointer(hTextureTexCoord, 2,
GLES20.GL_SHORT, false, 12, to.offset * 8 + offset + 8);
GlUtils.checkGlError("draw texture...");
GLES20.glVertexAttribPointer(hTextureTexCoord, 2,
GLES20.GL_SHORT, false, 12, off + 8);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, to.vertices,
GLES20.GL_UNSIGNED_SHORT, 0);
int numVertices = to.vertices - i;
if (numVertices > maxVertices)
numVertices = maxVertices;
GLES20.glDrawElements(GLES20.GL_TRIANGLES, numVertices,
GLES20.GL_UNSIGNED_SHORT, 0);
}
}
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
GlUtils.checkGlError("draw texture");
// GlUtils.checkGlError("< draw texture");
return layer.next;
}

View File

@ -308,7 +308,7 @@ public class TileGenerator implements IRenderCallback, IMapDatabaseCallback {
SymbolLayer sl = (SymbolLayer) mLayers.textureLayers;
SymbolItem it = new SymbolItem();
SymbolItem it = SymbolItem.get();
it.x = mPoiX;
it.y = mPoiY;
it.bitmap = bitmap;

View File

@ -23,16 +23,13 @@ import org.oscim.core.Tile;
import org.oscim.generator.JobTile;
import org.oscim.renderer.layer.TextItem;
import org.oscim.renderer.layer.VertexPool;
import org.oscim.renderer.overlays.Overlay;
import org.oscim.view.MapView;
import org.oscim.view.MapViewPosition;
import android.util.FloatMath;
import android.util.Log;
// FIXME move GLSurfaceView in separate class
public class TileManager { // extends GLSurfaceView {
private final static String TAG = "TileManager";
public class TileManager {
static final String TAG = TileManager.class.getSimpleName();
private static final int MAX_TILES_IN_QUEUE = 40;
private static final int CACHE_THRESHOLD = 10;
@ -45,17 +42,12 @@ public class TileManager { // extends GLSurfaceView {
// new jobs for the MapWorkers
private static ArrayList<JobTile> mJobList;
// all tiles currently referenced
// all tiles
private static ArrayList<MapTile> mTiles;
// tiles that have new data to upload, see passTile()
private static ArrayList<MapTile> mTilesLoaded;
private static ArrayList<Overlay> mOverlays;
// TODO current boundary tiles, values used to check if position has
// changed for updating current tile list
private static boolean mInitial;
// private static MapPosition mCurPosition, mDrawPosition;
@ -67,8 +59,6 @@ public class TileManager { // extends GLSurfaceView {
private static float[] mTileCoords = new float[8];
// private static int[] mBoundaryTiles = new int[8];
static int mUpdateCnt;
static ReentrantLock tilelock = new ReentrantLock();
static Tiles mCurrentTiles;
@ -123,10 +113,6 @@ public class TileManager { // extends GLSurfaceView {
}
};
// why not try a pattern every now and then?
// but should do the same for GLRenderer.
// findbugs found that volatile thingy, though this class
// is created before any other thread starts
private static volatile TileManager SINGLETON;
public static TileManager create(MapView mapView) {
@ -147,27 +133,15 @@ public class TileManager { // extends GLSurfaceView {
}
private TileManager(MapView mapView) {
// super(context);
mMapView = mapView;
mMapViewPosition = mapView.getMapViewPosition();
// Log.d(TAG, "init GLSurfaceLayer");
// setEGLConfigChooser(new GlConfigChooser());
// setEGLContextClientVersion(2);
//
// // setDebugFlags(DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS);
// mRenderer = new GLRenderer(mMapView);
// setRenderer(mRenderer);
//
// // if (!debugFrameTime)
// setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
mJobList = new ArrayList<JobTile>();
mTiles = new ArrayList<MapTile>();
mTilesLoaded = new ArrayList<MapTile>(30);
mOverlays = new ArrayList<Overlay>(5);
// this is probably a good place to init these
VertexPool.init();
QuadTree.init();
@ -191,6 +165,7 @@ public class TileManager { // extends GLSurfaceView {
if (mMapView == null)
return;
// FIXME too messy!
if (clear || mInitial) {
// make sure onDrawFrame is not running
GLRenderer.drawlock.lock();
@ -209,10 +184,6 @@ public class TileManager { // extends GLSurfaceView {
QuadTree.init();
// TODO clear overlay items data
mOverlays.clear();
// mOverlays.add(new Overlay());
// set up TileData arrays that are passed to gl-thread
int num = mWidth;
if (mWidth < mHeight)
@ -246,11 +217,13 @@ public class TileManager { // extends GLSurfaceView {
}
float s = Tile.TILE_SIZE;
// load some additional tiles more than currently visible
// load some tiles more than currently visible
// TODO limit how many more...
float scale = mapPosition.scale * 0.75f;
float px = (float) mapPosition.x;
float py = (float) mapPosition.y;
// TODO hint whether to prefetch parent / children
int zdir = 0;
for (int i = 0; i < 8; i += 2) {
@ -258,9 +231,6 @@ public class TileManager { // extends GLSurfaceView {
coords[i + 1] = (py + coords[i + 1] / scale) / s;
}
// TODO all following should probably be done in an idler instead
// to drain queued events. need to check how android handles things.
boolean changed = updateVisibleList(mapPosition, zdir);
mMapView.render();
@ -276,6 +246,8 @@ public class TileManager { // extends GLSurfaceView {
}
public static Tiles getActiveTiles(Tiles td) {
if (mCurrentTiles == null)
return td;
if (td != null && td.serial == mUpdateCnt)
return td;
@ -409,58 +381,65 @@ public class TileManager { // extends GLSurfaceView {
tile = QuadTree.getTile(x, y, zoomLevel);
if (tile == null) {
tile = new MapTile(x, y, zoomLevel);
QuadTree.add(tile);
if (tile != null) {
if (!tile.isActive())
mJobList.add(tile);
mTiles.add(tile);
mJobList.add(tile);
tileCounter++;
} else if (!tile.isActive()) {
mJobList.add(tile);
return tile;
}
// mNewTiles.tiles[tiles++] = tile;
// if (fetchChildren) {
// byte z = (byte) (zoomLevel + 1);
// for (int i = 0; i < 4; i++) {
// int cx = (xx << 1) + (i % 2);
// int cy = (yy << 1) + (i >> 1);
//
// MapTile c = QuadTree.getTile(cx, cy, z);
//
// if (c == null) {
// c = new MapTile(cx, cy, z);
//
// QuadTree.add(c);
// mTiles.add(c);
// }
//
// if (!c.isActive()) {
// mJobList.add(c);
// }
// }
// }
tile = new MapTile(x, y, zoomLevel);
QuadTree.add(tile);
// if (fetchParent || (!fetchProxy && zdir > 0 && zoomLevel > 0)) {
mTiles.add(tile);
mJobList.add(tile);
tileCounter++;
// if (zdir > 0 && zoomLevel > 0) {
// // prefetch parent
// MapTile p = tile.rel.parent.tile;
//
// if (p == null) {
// p = new MapTile(x >> 1, y >> 1, (byte) (zoomLevel - 1));
//
// QuadTree.add(p);
// mTiles.add(p);
// mJobList.add(p);
//
// } else if (!p.isActive()) {
// if (!mJobList.contains(p))
// mJobList.add(p);
// }
// }
return tile;
// mNewTiles.tiles[tiles++] = tile;
// boolean fetchParent = false;
// boolean fetchProxy = false;
// boolean fetchChildren = false;
// if (fetchChildren) {
// byte z = (byte) (zoomLevel + 1);
// for (int i = 0; i < 4; i++) {
// int cx = (x << 1) + (i % 2);
// int cy = (y << 1) + (i >> 1);
//
// MapTile c = QuadTree.getTile(cx, cy, z);
//
// if (c == null) {
// c = new MapTile(cx, cy, z);
//
// QuadTree.add(c);
// mTiles.add(c);
// }
//
// if (!c.isActive()) {
// mJobList.add(c);
// }
// }
// }
//
// if (fetchParent || (!fetchProxy && zdir > 0 && zoomLevel > 0)) {
// if (zdir > 0 && zoomLevel > 0) {
// // prefetch parent
// MapTile p = tile.rel.parent.tile;
//
// if (p == null) {
// p = new MapTile(x >> 1, y >> 1, (byte) (zoomLevel - 1));
//
// QuadTree.add(p);
// mTiles.add(p);
// mJobList.add(p);
//
// } else if (!p.isActive()) {
// if (!mJobList.contains(p))
// mJobList.add(p);
// }
// }
// }
}
private static void clearTile(MapTile t) {
@ -492,7 +471,7 @@ public class TileManager { // extends GLSurfaceView {
byte zoom = mapPosition.zoomLevel;
long x = (long) mapPosition.x;
long y = (long) mapPosition.y;
// long center = Tile.TILE_SIZE << (zoom - 1);
long center = Tile.TILE_SIZE << (zoom - 1);
int diff;
long dx, dy;
@ -506,11 +485,10 @@ public class TileManager { // extends GLSurfaceView {
if (diff == 0) {
dx = (t.pixelX + h) - x;
dy = (t.pixelY + h) - y;
// t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)) *
// 0.25f;
// dx %= center;
// dy %= center;
t.distance = FloatMath.sqrt((dx * dx + dy * dy)) * 0.25f;
dx %= center;
dy %= center;
t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)) * 0.25f;
// t.distance = FloatMath.sqrt((dx * dx + dy * dy)) * 0.25f;
} else if (diff > 0) {
// tile zoom level is child of current
@ -522,20 +500,19 @@ public class TileManager { // extends GLSurfaceView {
dx = ((t.pixelX + h) >> (diff >> 1)) - x;
dy = ((t.pixelY + h) >> (diff >> 1)) - y;
}
// dx %= center;
// dy %= center;
// t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy));
t.distance = FloatMath.sqrt((dx * dx + dy * dy));
dx %= center;
dy %= center;
t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy));
// t.distance = FloatMath.sqrt((dx * dx + dy * dy));
} else {
// tile zoom level is parent of current
dx = ((t.pixelX + h) << -diff) - x;
dy = ((t.pixelY + h) << -diff) - y;
// dx %= center;
// dy %= center;
// t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)) *
// (-diff * 0.5f);
t.distance = FloatMath.sqrt((dx * dx + dy * dy)) * (-diff * 0.5f);
dx %= center;
dy %= center;
t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)) * (-diff * 0.5f);
// t.distance = FloatMath.sqrt((dx * dx + dy * dy)) * (-diff * 0.5f);
}
}
}
@ -565,10 +542,9 @@ public class TileManager { // extends GLSurfaceView {
for (int i = 1; i < remove; i++) {
MapTile t = mTiles.remove(size - i);
// synchronized (t) {
if (t.isLocked()) {
// dont remove tile used by GLRenderer
// dont remove tile used by GLRenderer, or somewhere else
Log.d(TAG, "X not removing " + t + " " + t.distance);
mTiles.add(t);
} else if (t.isLoading) {
@ -585,7 +561,6 @@ public class TileManager { // extends GLSurfaceView {
} else {
clearTile(t);
}
// }
}
}
@ -616,7 +591,7 @@ public class TileManager { // extends GLSurfaceView {
for (int i = 0, n = size - MAX_TILES_IN_QUEUE / 2; i < n; n--) {
MapTile t = mTilesLoaded.get(i);
// synchronized (t) {
if (t.isLocked()) {
// Log.d(TAG, "keep unused tile data: " + t + " " +
// t.isActive);
@ -629,7 +604,6 @@ public class TileManager { // extends GLSurfaceView {
mTiles.remove(t);
clearTile(t);
}
// }
}
}
@ -650,21 +624,22 @@ public class TileManager { // extends GLSurfaceView {
// return true;
// }
// synchronized (tile) {
if (tile.vbo == null) {
tile.vbo = BufferObject.get();
if (tile.vbo == null) {
Log.d(TAG, "no VBOs left for " + tile);
tile.isLoading = false;
return false;
}
} else
if (tile.vbo != null) {
// BAD Things(tm) happend...
Log.d(TAG, "tile loaded before " + tile);
return true;
}
tile.vbo = BufferObject.get();
if (tile.vbo == null) {
Log.d(TAG, "no VBOs left for " + tile);
tile.isLoading = false;
return true;
}
tile.newData = true;
tile.isLoading = false;
// }
mMapView.render();
@ -685,23 +660,4 @@ public class TileManager { // extends GLSurfaceView {
if (mWidth > 0 && mHeight > 0)
mInitial = true;
}
// public void setRenderTheme(RenderTheme t) {
// if (mRenderer != null)
// mRenderer.setRenderTheme(t);
//
// }
// @Override
// protected void onSizeChanged(int w, int h, int oldw, int oldh) {
//
// Log.d(TAG, "onSizeChanged" + w + " " + h);
// mWidth = w;
// mHeight = h;
//
// if (mWidth > 0 && mHeight > 0)
// mInitial = true;
//
// super.onSizeChanged(w, h, oldw, oldh);
// }
}

View File

@ -35,6 +35,5 @@ public abstract class Layer {
VertexPoolItem pool;
protected VertexPoolItem curItem;
protected void clear() {
}
abstract protected void clear();
}

View File

@ -118,6 +118,10 @@ public class Layers {
TextureLayer sl = (TextureLayer) l;
sl.compile(sbuf);
}
// FIXME
addLayerItems(sbuf, textureLayers, Layer.SYMBOL, 0);
}
private static void addLayerItems(ShortBuffer sbuf, Layer l, byte type, int pos) {
@ -162,14 +166,22 @@ public class Layers {
layers = layers.next;
}
while (textureLayers != null) {
textureLayers.clear();
Layer l = textureLayers;
while (l != null) {
// TextureLayer sl = (TextureLayer) textureLayers;
// if (sl.textures != null)
// TextureObject.release(sl.textures);
l.clear();
textureLayers = textureLayers.next;
if (l.pool != null) {
VertexPool.release(l.pool);
l.pool = null;
l.curItem = null;
}
// if (l instanceof TextLayer)
// ((TextLayer) l).clear();
l = l.next;
}
textureLayers = null;
}
}

View File

@ -49,11 +49,18 @@ public final class LineLayer extends Layer {
outlines = link;
}
/*
/**
* line extrusion is based on code from GLMap
* (https://github.com/olofsj/GLMap/) by olofsj
*
* @param points
* array of points as float x_n = i, y_n = i+1
* @param index
* array of line indices holding the length of the individual
* lines
* @param closed
* whether to connect start- and end-point
*/
public void addLine(float[] points, short[] index, boolean closed) {
float x, y, nextX, nextY, prevX, prevY;
float a, ux, uy, vx, vy, wx, wy;
@ -83,7 +90,7 @@ public final class LineLayer extends Layer {
if (length < 0)
break;
// save some vertices
// Note: just a hack to save some vertices
if (rounded && i > 200)
rounded = false;
@ -520,4 +527,8 @@ public final class LineLayer extends Layer {
si.used = opos;
curItem = si;
}
@Override
protected void clear() {
}
}

View File

@ -87,4 +87,7 @@ public final class PolygonLayer extends Layer {
curItem = si;
}
@Override
protected void clear() {
}
}

View File

@ -15,15 +15,55 @@
package org.oscim.renderer.layer;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
public class SymbolItem {
private static Object lock = new Object();
private static SymbolItem pool;
public static SymbolItem get() {
synchronized (lock) {
if (pool == null)
return new SymbolItem();
SymbolItem ti = pool;
pool = pool.next;
ti.next = null;
return ti;
}
}
public static void release(SymbolItem ti) {
if (ti == null)
return;
synchronized (lock) {
while (ti != null) {
SymbolItem next = ti.next;
ti.drawable = null;
ti.bitmap = null;
ti.next = pool;
pool = ti;
ti = next;
}
}
}
SymbolItem next;
public Bitmap bitmap;
public Drawable drawable;
public float x;
public float y;
public boolean billboard;
public int state;
// center, top, bottom, left, right, top-left...
byte placement;
// byte placement;
}

View File

@ -20,34 +20,37 @@ import org.oscim.renderer.TextureObject;
import org.oscim.renderer.TextureRenderer;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.Log;
// TODO share one static texture for all poi map symabols
public final class SymbolLayer extends TextureLayer {
private static String TAG = SymbolLayer.class.getSimpleName();
private final static String TAG = SymbolLayer.class.getSimpleName();
private final static int TEXTURE_WIDTH = TextureObject.TEXTURE_WIDTH;
private final static int TEXTURE_HEIGHT = TextureObject.TEXTURE_HEIGHT;
private final static float SCALE = 8.0f;
private static short[] mVertices;
SymbolItem symbols;
private Canvas mCanvas;
private Rect mRect = new Rect();
public SymbolLayer() {
if (mVertices == null)
mVertices = new short[TextureRenderer.MAX_ITEMS * 24];
type = Layer.SYMBOL;
fixed = true;
mCanvas = new Canvas();
}
public void addSymbol(SymbolItem item) {
verticesCnt += 4;
SymbolItem it = symbols;
for (; it != null; it = it.next) {
for (SymbolItem it = symbols; it != null; it = it.next) {
if (it.bitmap == item.bitmap) {
// insert after same bitmap
item.next = it.next;
it.next = item;
return;
@ -58,30 +61,77 @@ public final class SymbolLayer extends TextureLayer {
symbols = item;
}
public void addDrawable(Drawable drawable, int state, float x, float y) {
verticesCnt += 4;
SymbolItem item = SymbolItem.get();
item.drawable = drawable;
item.x = x;
item.y = y;
item.billboard = true;
item.state = state;
for (SymbolItem it = symbols; it != null; it = it.next) {
if (it.drawable == item.drawable && it.state == item.state) {
// insert after same drawable
item.next = it.next;
it.next = item;
return;
}
}
item.next = symbols;
symbols = item;
}
@Override
void compile(ShortBuffer sbuf) {
if (TextureRenderer.debug)
Log.d("...", "compile");
for (TextureObject to = textures; to != null; to = to.next)
TextureObject.uploadTexture(to);
}
private final static int LBIT_MASK = 0xfffffffe;
// TODO ... reuse texture when only symbol position changed
@Override
public void compile(ShortBuffer sbuf) {
public boolean prepare() {
short numIndices = 0;
short offsetIndices = 0;
short curIndices = 0;
int pos = 0;
short buf[] = mVertices;
int bufLen = buf.length;
curItem = VertexPool.get();
pool = curItem;
VertexPoolItem si = curItem;
int pos = si.used;
short buf[] = si.vertices;
int advanceY = 0;
float x = 0;
float y = 0;
Canvas canvas = TextureObject.getCanvas();
TextureObject to = TextureObject.get();
textures = to;
mCanvas.setBitmap(to.bitmap);
int maxIndices = TextureRenderer.MAX_ITEMS * TextureRenderer.INDICES_PER_SPRITE;
for (SymbolItem it = symbols; it != null;) {
float width, height;
// add bitmap
float width = it.bitmap.getWidth();
float height = it.bitmap.getHeight();
if (it.bitmap != null) {
// add bitmap
width = it.bitmap.getWidth();
height = it.bitmap.getHeight();
} else {
width = it.drawable.getIntrinsicWidth();
height = it.drawable.getIntrinsicHeight();
}
if (height > advanceY)
advanceY = (int) height;
@ -91,43 +141,67 @@ public final class SymbolLayer extends TextureLayer {
y += advanceY;
advanceY = (int) (height + 0.5f);
if (y + height > TEXTURE_HEIGHT) {
Log.d(TAG, "reached max symbols");
TextureObject to = TextureObject.uploadCanvas(offsetIndices, numIndices);
offsetIndices = numIndices;
to.next = textures;
textures = to;
sbuf.put(buf, 0, pos);
pos = 0;
x = 0;
y = 0;
advanceY = (int) height;
}
}
canvas.drawBitmap(it.bitmap, x, y, null);
if (y + height > TEXTURE_HEIGHT || curIndices == maxIndices) {
Log.d(TAG, "reached max symbols: " + numIndices);
float hw = width / 2.0f;
float hh = height / 2.0f;
to.offset = offsetIndices;
to.vertices = curIndices;
short x1 = (short) (SCALE * (-hw));
short x2 = (short) (SCALE * (hw));
short y1 = (short) (SCALE * (hh));
short y2 = (short) (SCALE * (-hh));
numIndices += curIndices;
offsetIndices = numIndices;
curIndices = 0;
to.next = TextureObject.get();
to = to.next;
mCanvas.setBitmap(to.bitmap);
x = 0;
y = 0;
advanceY = (int) height;
}
if (it.bitmap != null) {
mCanvas.drawBitmap(it.bitmap, x, y, null);
} else {
it.drawable.copyBounds(mRect);
it.drawable.setBounds((int) x, (int) y, (int) (x + width), (int) (y + height));
it.drawable.draw(mCanvas);
it.drawable.setBounds(mRect);
}
short x1, y1, x2, y2;
if (it.bitmap != null) {
float hw = width / 2.0f;
float hh = height / 2.0f;
x1 = (short) (SCALE * (-hw));
x2 = (short) (SCALE * (hw));
y1 = (short) (SCALE * (hh));
y2 = (short) (SCALE * (-hh));
} else {
// use drawable offsets (for marker hotspot)
x2 = (short) (SCALE * (mRect.left));
y2 = (short) (SCALE * (mRect.top));
x1 = (short) (SCALE * (mRect.right));
y1 = (short) (SCALE * (mRect.bottom));
}
short u1 = (short) (SCALE * x);
short v1 = (short) (SCALE * y);
short u2 = (short) (SCALE * (x + width));
short v2 = (short) (SCALE * (y + height));
// add symbol items referencing the same bitmap
// add symbol items referencing the same bitmap / drawable
for (SymbolItem it2 = it;; it2 = it2.next) {
if (it2 == null || it2.bitmap != it.bitmap) {
if (it2 == null
// || (curIndices == maxIndices)
|| (it.drawable != null && it2.drawable != it.drawable)
|| (it.bitmap != null && it2.bitmap != it.bitmap)) {
it = it2;
break;
}
@ -166,24 +240,41 @@ public final class SymbolLayer extends TextureLayer {
buf[pos++] = v1;
// six elements used to draw the four vertices
numIndices += 6;
curIndices += TextureRenderer.INDICES_PER_SPRITE;
if (pos == VertexPoolItem.SIZE) {
si.used = VertexPoolItem.SIZE;
si = si.next = VertexPool.get();
buf = si.vertices;
pos = 0;
}
// FIXME this does not work, need to draw bitmap on next
// texture...
if (pos == bufLen) {
sbuf.put(buf, 0, pos);
pos = 0;
}
// if (pos == bufLen) {
// sbuf.put(buf, 0, pos);
// pos = 0;
// }
x += width;
}
}
TextureObject to = TextureObject.uploadCanvas(offsetIndices, numIndices);
to.offset = offsetIndices;
to.vertices = curIndices;
to.next = textures;
textures = to;
si.used = pos;
curItem = si;
sbuf.put(buf, 0, pos);
return true;
}
@Override
protected void clear() {
TextureObject.release(textures);
SymbolItem.release(symbols);
textures = null;
symbols = null;
verticesCnt = 0;
}
}

View File

@ -27,7 +27,9 @@ public class TextItem {
TextItem ti = pool;
pool = pool.next;
ti.next = null;
return ti;
}
}
@ -73,5 +75,6 @@ public class TextItem {
public Text text;
public float width;
public short x1, y1, x2, y2;
// public byte placement
}

View File

@ -25,26 +25,28 @@ import android.util.Log;
public final class TextLayer extends TextureLayer {
private static String TAG = TextureLayer.class.getSimpleName();
// private static String TAG = TextureLayer.class.getSimpleName();
private final static int TEXTURE_WIDTH = TextureObject.TEXTURE_WIDTH;
private final static int TEXTURE_HEIGHT = TextureObject.TEXTURE_HEIGHT;
private final static float SCALE = 8.0f;
private final static int LBIT_MASK = 0xfffffffe;
private static short[] mVertices;
private static int mFontPadX = 1;
private static int mFontPadY = 1;
TextItem labels;
private Canvas mCanvas;
public TextItem getLabels() {
return labels;
}
public TextLayer() {
if (mVertices == null)
mVertices = new short[TextureRenderer.MAX_ITEMS * 24];
type = Layer.SYMBOL;
mCanvas = new Canvas();
fixed = true;
}
public void addText(TextItem item) {
@ -53,8 +55,10 @@ public final class TextLayer extends TextureLayer {
for (; it != null; it = it.next) {
if (it.text == item.text) {
// insert after text of same type
item.next = it.next;
it.next = item;
return;
}
}
@ -64,24 +68,44 @@ public final class TextLayer extends TextureLayer {
}
@Override
public void compile(ShortBuffer sbuf) {
int numLabel = 0;
void compile(ShortBuffer sbuf) {
if (TextureRenderer.debug)
Log.d("...", "compile");
for (TextureObject to = textures; to != null; to = to.next)
TextureObject.uploadTexture(to);
}
@Override
public boolean prepare() {
if (TextureRenderer.debug)
Log.d("...", "prepare");
// int numLabel = 0;
// int numTextures = 0;
short numIndices = 0;
short offsetIndices = 0;
int pos = 0;
short buf[] = mVertices;
int bufLen = buf.length;
curItem = VertexPool.get();
pool = curItem;
VertexPoolItem si = curItem;
int pos = si.used;
short buf[] = si.vertices;
int advanceY = 0;
float x = 0;
float y = 0;
float yy;
Canvas canvas = TextureObject.getCanvas();
TextureObject to = TextureObject.get();
textures = to;
mCanvas.setBitmap(to.bitmap);
for (TextItem it = labels; it != null; it = it.next) {
numLabel++;
// numLabel++;
float width = it.width + 2 * mFontPadX;
float height = (int) (it.text.fontHeight) + 2 * mFontPadY + 0.5f;
@ -99,33 +123,27 @@ public final class TextLayer extends TextureLayer {
// numLabel + " "
// + ((numIndices - offsetIndices) / 6));
// need to sync bitmap upload somehow???
TextureObject to = TextureObject.uploadCanvas(offsetIndices, numIndices);
to.offset = offsetIndices;
to.vertices = (short) (numIndices - offsetIndices);
offsetIndices = numIndices;
to.next = textures;
textures = to;
to.next = TextureObject.get();
to = to.next;
sbuf.put(buf, 0, pos);
pos = 0;
mCanvas.setBitmap(to.bitmap);
x = 0;
y = 0;
advanceY = (int) height;
// clear bitmap, TODO rotate two canvas to reduce the chance
// of having upload lock draing to the canvas?
canvas = TextureObject.getCanvas();
// numTextures++;
}
}
yy = y + (height - 1) - it.text.fontDescent - mFontPadY;
if (it.text.stroke != null)
canvas.drawText(it.string, x + it.width / 2, yy, it.text.stroke);
mCanvas.drawText(it.string, x + it.width / 2, yy, it.text.stroke);
canvas.drawText(it.string, x + it.width / 2, yy, it.text.paint);
mCanvas.drawText(it.string, x + it.width / 2, yy, it.text.paint);
// FIXME !!!
if (width > TEXTURE_WIDTH)
@ -207,30 +225,41 @@ public final class TextLayer extends TextureLayer {
// six indices to draw the four vertices
numIndices += 6;
// FIXME this does not work, need to draw bitmap on next
// texture...
if (pos == bufLen) {
Log.d(TAG, "--- reached max label per texture " + numLabel);
sbuf.put(buf, 0, pos);
if (pos == VertexPoolItem.SIZE) {
si.used = VertexPoolItem.SIZE;
si = si.next = VertexPool.get();
buf = si.vertices;
pos = 0;
}
// FIXME this does not work, need to draw bitmap on next
// texture...
// if (numLabel == TextureRenderer.MAX_ITEMS) {
// Log.d(TAG, "--- reached max label per texture " + numLabel);
// sbuf.put(buf, 0, pos);
// pos = 0;
// }
x += width;
}
TextureObject to = TextureObject.uploadCanvas(offsetIndices, numIndices);
to.offset = offsetIndices;
to.vertices = (short) (numIndices - offsetIndices);
to.next = textures;
textures = to;
sbuf.put(buf, 0, pos);
si.used = pos;
curItem = si;
// Log.d(TAG, "added labels " + numTextures + " " + numLabel);
return true;
}
@Override
protected void clear() {
TextureObject.release(textures);
TextItem.release(labels);
textures = null;
labels = null;
verticesCnt = 0;
}
}

View File

@ -20,12 +20,13 @@ import org.oscim.renderer.TextureObject;
public abstract class TextureLayer extends Layer {
public TextureObject textures;
public boolean fixed;
/**
* @param sbuf
* buffer to add vertices
*/
void compile(ShortBuffer sbuf) {
}
abstract void compile(ShortBuffer sbuf);
abstract public boolean prepare();
}

View File

@ -15,15 +15,12 @@
package org.oscim.renderer.layer;
public class VertexPoolItem {
final short[] vertices;
final short[] vertices = new short[SIZE];
int used;
VertexPoolItem next;
VertexPoolItem() {
vertices = new short[SIZE];
used = 0;
}
// must be multiple of 4 (expected in LineLayer/PolygonLayer)
static final int SIZE = 256;
// and 24 (Texture Layer)
static final int SIZE = 360;
}

View File

@ -14,6 +14,7 @@
*/
package org.oscim.renderer.overlays;
import org.oscim.core.MapPosition;
import org.oscim.core.Tile;
import org.oscim.renderer.layer.Layer;
import org.oscim.renderer.layer.LineLayer;
@ -27,13 +28,13 @@ import android.graphics.Color;
import android.graphics.Paint.Cap;
import android.util.Log;
public class OverlayGrid extends Overlay {
public class OverlayGrid extends RenderOverlay {
private float[] mPoints;
private short[] mIndex;
private Text mText;
OverlayGrid(MapView mapView) {
public OverlayGrid(MapView mapView) {
super(mapView);
int size = Tile.TILE_SIZE;
@ -91,6 +92,8 @@ public class OverlayGrid extends Overlay {
tl.addText(ti);
}
}
tl.prepare();
layers.textureLayers = tl;
}
@ -107,7 +110,7 @@ public class OverlayGrid extends Overlay {
}
@Override
public synchronized void update(boolean positionChanged, boolean tilesChanged) {
public synchronized void update(MapPosition curPos, boolean positionChanged, boolean tilesChanged) {
updateMapPosition();
@ -134,6 +137,8 @@ public class OverlayGrid extends Overlay {
ll.width = 1.5f;
ll.addLine(mPoints, mIndex, false);
Log.d("...", "update labels");
addLabels(x, y, mCurZ);
newData = true;

View File

@ -14,16 +14,16 @@
*/
package org.oscim.renderer.overlays;
import org.oscim.renderer.layer.Layer;
import org.oscim.renderer.layer.LineLayer;
import java.io.IOException;
import org.oscim.core.MapPosition;
import org.oscim.renderer.layer.SymbolItem;
import org.oscim.renderer.layer.SymbolLayer;
import org.oscim.renderer.layer.TextItem;
import org.oscim.theme.renderinstruction.Line;
import org.oscim.theme.renderinstruction.BitmapUtils;
import org.oscim.view.MapView;
import android.graphics.Color;
import android.graphics.Paint.Cap;
public class OverlayTest extends Overlay {
public class OverlayTest extends RenderOverlay {
TextItem labels;
@ -31,15 +31,16 @@ public class OverlayTest extends Overlay {
private boolean first = true;
OverlayTest(MapView mapView) {
public OverlayTest(MapView mapView) {
super(mapView);
LineLayer ll = (LineLayer) layers.getLayer(1, Layer.LINE);
ll.line = new Line(Color.BLUE, 1.0f, Cap.BUTT);
ll.width = 2;
float[] points = { -100, -100, 100, -100, 100, 100, -100, 100, -100, -100 };
short[] index = { (short) points.length };
ll.addLine(points, index, false);
// LineLayer ll = (LineLayer) layers.getLayer(1, Layer.LINE);
// ll.line = new Line(Color.BLUE, 1.0f, Cap.BUTT);
// ll.width = 2;
// float[] points = { -100, -100, 100, -100, 100, 100, -100, 100, -100, -100 };
// short[] index = { (short) points.length };
// ll.addLine(points, index, false);
//
// PolygonLayer pl = (PolygonLayer) layers.getLayer(0, Layer.POLYGON);
// pl.area = new Area(Color.argb(128, 255, 0, 0));
@ -53,30 +54,33 @@ public class OverlayTest extends Overlay {
// short[] pindex = { (short) ppoints.length };
// pl.addPolygon(ppoints, pindex);
// SymbolLayer sl = new SymbolLayer();
// SymbolItem it = new SymbolItem();
//
// it.x = 0;
// it.y = 0;
// // billboard always faces camera
// it.billboard = true;
//
// try {
// it.bitmap = BitmapUtils.createBitmap("file:/sdcard/cheshire.png");
// } catch (IOException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// sl.addSymbol(it);
//
// SymbolItem it2 = new SymbolItem();
// it2.bitmap = it.bitmap;
// it2.x = 0;
// it2.y = 0;
// // billboard always faces camera
// it2.billboard = false;
//
// sl.addSymbol(it2);
SymbolLayer sl = new SymbolLayer();
SymbolItem it = new SymbolItem();
it.x = 0;
it.y = 0;
// billboard always faces camera
it.billboard = true;
try {
it.bitmap = BitmapUtils.createBitmap("file:/sdcard/cheshire.png");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sl.addSymbol(it);
SymbolItem it2 = new SymbolItem();
it2.bitmap = it.bitmap;
it2.x = 0;
it2.y = 0;
// billboard always faces camera
it2.billboard = false;
sl.addSymbol(it2);
sl.fixed = false;
layers.textureLayers = sl;
// TextLayer tl = new TextLayer();
// Text t = Text.createText(20, 2, Color.WHITE, Color.BLACK, false);
@ -92,14 +96,16 @@ public class OverlayTest extends Overlay {
}
@Override
public synchronized void update(boolean positionChanged, boolean tilesChanged) {
public synchronized void update(MapPosition curPos, boolean positionChanged, boolean tilesChanged) {
// keep position constant (or update layer relative to new position)
mMapView.getMapViewPosition().getMapPosition(mMapPosition, null);
//mMapView.getMapViewPosition().getMapPosition(mMapPosition, null);
if (first) {
// fix at initial position
// mapView.getMapViewPosition().getMapPosition(mMapPosition, null);
updateMapPosition();
first = false;
((SymbolLayer) (layers.textureLayers)).prepare();
// pass layers to be uploaded and drawn to GL Thread
// afterwards never modify 'layers' outside of this function!

View File

@ -17,8 +17,8 @@ package org.oscim.renderer.overlays;
import org.oscim.core.MapPosition;
import org.oscim.core.Tile;
import org.oscim.renderer.TileManager;
import org.oscim.renderer.MapTile;
import org.oscim.renderer.TileManager;
import org.oscim.renderer.Tiles;
import org.oscim.renderer.layer.TextItem;
import org.oscim.renderer.layer.TextLayer;
@ -29,20 +29,23 @@ import org.oscim.view.MapView;
import android.os.SystemClock;
import android.util.FloatMath;
public class OverlayText extends Overlay {
public class OverlayText extends RenderOverlay {
private Tiles tiles;
private LabelThread mThread;
/* package */boolean mRun;
/* package */boolean mRerun;
private MapPosition mWorkPos;
private TextLayer mWorkLayer;
private TextLayer mNewLayer;
/* package */boolean mRun;
/* package */boolean mRerun;
class LabelThread extends PausableThread {
@Override
protected void doWork() {
SystemClock.sleep(250);
SystemClock.sleep(300);
mRun = false;
updateLabels();
mMapView.redrawMap();
@ -70,14 +73,29 @@ public class OverlayText extends Overlay {
void updateLabels() {
tiles = TileManager.getActiveTiles(tiles);
// Log.d("...", "relabel " + mRerun + " " + x + " " + y);
if (tiles.cnt == 0)
return;
mMapView.getMapViewPosition().getMapPosition(mWorkPos, null);
// TODO tiles might be from another zoomlevel than the current:
TextLayer tl = mWorkLayer;
if (tl == null)
tl = new TextLayer();
// tiles might be from another zoomlevel than the current:
// this scales MapPosition to the zoomlevel of tiles...
// TODO create a helper function in MapPosition
int diff = tiles.tiles[0].zoomLevel - mWorkPos.zoomLevel;
if (diff > 1 || diff < -2) {
synchronized (this) {
mNewLayer = tl;
}
return;
}
float div = FastMath.pow(diff);
// fix map position to tile coordinates
@ -89,14 +107,11 @@ public class OverlayText extends Overlay {
mWorkPos.zoomLevel += diff;
mWorkPos.scale = div;
// Log.d("...", "relabel " + mRerun + " " + x + " " + y);
TextLayer tl = new TextLayer();
float angle = (float) Math.toRadians(mWorkPos.angle);
float cos = FloatMath.cos(angle);
float sin = FloatMath.sin(angle);
// TODO more sophisticated placement :)
for (int i = 0, n = tiles.cnt; i < n; i++) {
MapTile t = tiles.tiles[i];
if (!t.isVisible)
@ -104,7 +119,6 @@ public class OverlayText extends Overlay {
int dx = (t.tileX - x) * Tile.TILE_SIZE;
int dy = (t.tileY - y) * Tile.TILE_SIZE;
// Log.d("...", "update tiles " + dx + " " + dy);
for (TextItem ti = t.labels; ti != null; ti = ti.next) {
@ -129,20 +143,27 @@ public class OverlayText extends Overlay {
}
}
// draw text to bitmaps and create vertices
tl.prepare();
// everything synchronized?
synchronized (this) {
mWorkLayer = tl;
mNewLayer = tl;
}
}
@Override
public synchronized void update(boolean positionChanged, boolean tilesChanged) {
public synchronized void update(MapPosition curPos, boolean positionChanged, boolean tilesChanged) {
// Log.d("...", "update " + tilesChanged + " " + positionChanged);
if (mWorkLayer != null) {
if (mNewLayer != null) {
// keep text layer, not recrating its canvas each time...
mWorkLayer = (TextLayer) layers.textureLayers;
layers.clear();
layers.textureLayers = mWorkLayer;
mWorkLayer = null;
layers.textureLayers = mNewLayer;
mNewLayer = null;
// make the 'labeled' MapPosition current
MapPosition tmp = mMapPosition;
@ -154,7 +175,6 @@ public class OverlayText extends Overlay {
}
if (tilesChanged || positionChanged) {
if (!mRun) {
mRun = true;
synchronized (mThread) {

View File

@ -28,12 +28,14 @@ import org.oscim.view.MapView;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.util.Log;
public abstract class Overlay {
public abstract class RenderOverlay {
protected final MapView mMapView;
// keep the Position for which the Overlay is rendered
protected MapPosition mMapPosition;
// current Layers to draw
public final Layers layers;
// flag to set when data is ready for (re)compilation.
@ -44,19 +46,14 @@ public abstract class Overlay {
public BufferObject vbo;
public Overlay(MapView mapView) {
public RenderOverlay(MapView mapView) {
mMapView = mapView;
mMapPosition = new MapPosition();
layers = new Layers();
}
synchronized boolean onTouch(boolean down) {
Log.d("...", "Overlay handle onTouch " + down);
return true;
}
/**
* update mMapPosition
* Utility: update mMapPosition
*
* @return true if position has changed
*/
@ -68,12 +65,13 @@ public abstract class Overlay {
// use synchronized (this){} when updating 'layers' from another thread
/**
* @param curPos TODO
* @param positionChanged
* true when MapPosition has changed
* @param tilesChanged
* true when loaded tiles changed
*/
public synchronized void update(boolean positionChanged, boolean tilesChanged) {
public synchronized void update(MapPosition curPos, boolean positionChanged, boolean tilesChanged) {
// // keep position constant (or update layer relative to new position)
// mMapView.getMapViewPosition().getMapPosition(mMapPosition, null);
//
@ -110,23 +108,20 @@ public abstract class Overlay {
// float scale = curPos.scale / div;
for (Layer l = layers.textureLayers; l != null;) {
l = TextureRenderer.draw(l, (mMapPosition.scale / pos.scale) * div, proj, mv,
layers.texOffset);
}
}
private float setMatrix(MapPosition curPos, float[] matrix) {
// TODO if oPos == curPos this could be simplified
MapPosition oPos = mMapPosition;
byte z = oPos.zoomLevel;
// int diff = curPos.zoomLevel - z;
float div = FastMath.pow(z - curPos.zoomLevel);
// if (diff < 0)
// div = (1 << -diff);
// else if (diff > 0)
// div = (1.0f / (1 << diff));
float div = FastMath.pow(z - curPos.zoomLevel);
float x = (float) (oPos.x - curPos.x * div);
float y = (float) (oPos.y - curPos.y * div);
@ -145,8 +140,8 @@ public abstract class Overlay {
matrix[12] = x * scale;
matrix[13] = y * scale;
// scale to current tile world coordinates
scale = (curPos.scale / oPos.scale) / div;
// scale to tile to world coordinates
scale /= GLRenderer.COORD_MULTIPLIER;
matrix[0] = scale;
matrix[5] = scale;

View File

@ -33,8 +33,7 @@ public final class BitmapUtils {
if (src.startsWith(PREFIX_JAR)) {
String name = "/org/oscim/theme/osmarender/" + src.substring(PREFIX_JAR.length());
InputStream inputStream = BitmapUtils.class
.getResourceAsStream(name);
InputStream inputStream = BitmapUtils.class.getResourceAsStream(name);
if (inputStream == null) {
throw new FileNotFoundException("resource not found: " + src + " " + name);
}

View File

@ -20,7 +20,8 @@ package org.oscim.utils;
*/
public final class GeometryUtils {
/**
* Calculates the center of the minimum bounding rectangle for the given coordinates.
* Calculates the center of the minimum bounding rectangle for the given
* coordinates.
*
* @param coordinates
* the coordinates for which calculation should be done.
@ -55,14 +56,16 @@ public final class GeometryUtils {
* @return true if the given way is closed, false otherwise.
*/
static boolean isClosedWay(float[] way) {
return Float.compare(way[0], way[way.length - 2]) == 0 && Float.compare(way[1], way[way.length - 1]) == 0;
return Float.compare(way[0], way[way.length - 2]) == 0
&& Float.compare(way[1], way[way.length - 1]) == 0;
}
private GeometryUtils() {
throw new IllegalStateException();
}
static boolean linesIntersect(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) {
static boolean linesIntersect(double x1, double y1, double x2, double y2, double x3, double y3,
double x4, double y4) {
// Return false if either of the lines have zero length
if (x1 == x2 && y1 == y2 || x3 == x4 && y3 == y4) {
return false;
@ -111,9 +114,11 @@ public final class GeometryUtils {
// since p1-p2 is parallel with p3-p4
if (collinearityTestForP3 == 0) {
// The lines are collinear. Now check if they overlap.
if (x1 >= x3 && x1 <= x4 || x1 <= x3 && x1 >= x4 || x2 >= x3 && x2 <= x4 || x2 <= x3 && x2 >= x4
if (x1 >= x3 && x1 <= x4 || x1 <= x3 && x1 >= x4 || x2 >= x3 && x2 <= x4
|| x2 <= x3 && x2 >= x4
|| x3 >= x1 && x3 <= x2 || x3 <= x1 && x3 >= x2) {
if (y1 >= y3 && y1 <= y4 || y1 <= y3 && y1 >= y4 || y2 >= y3 && y2 <= y4 || y2 <= y3 && y2 >= y4
if (y1 >= y3 && y1 <= y4 || y1 <= y3 && y1 >= y4 || y2 >= y3 && y2 <= y4
|| y2 <= y3 && y2 >= y4
|| y3 >= y1 && y3 <= y2 || y3 <= y1 && y3 >= y2) {
return true;
}
@ -124,7 +129,8 @@ public final class GeometryUtils {
return true;
}
static boolean doesIntersect(double l1x1, double l1y1, double l1x2, double l1y2, double l2x1, double l2y1,
static boolean doesIntersect(double l1x1, double l1y1, double l1x2, double l1y2, double l2x1,
double l2y1,
double l2x2, double l2y2) {
double denom = ((l2y2 - l2y1) * (l1x2 - l1x1)) - ((l2x2 - l2x1) * (l1y2 - l1y1));
@ -157,7 +163,8 @@ public final class GeometryUtils {
* ...
* @return ...
*/
public static boolean lineIntersect(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) {
public static boolean lineIntersect(int x1, int y1, int x2, int y2, int x3, int y3, int x4,
int y4) {
double denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (denom == 0.0) { // Lines are parallel.
return false;
@ -171,108 +178,4 @@ public final class GeometryUtils {
return false;
}
// private static final int OUT_LEFT = 1;
// private static final int OUT_TOP = 2;
// private static final int OUT_RIGHT = 4;
// private static final int OUT_BOTTOM = 8;
//
//
// private static int outcode(double x, double y) {
// /*
// * Note on casts to double below. If the arithmetic of
// * x+w or y+h is done in float, then some bits may be
// * lost if the binary exponents of x/y and w/h are not
// * similar. By converting to double before the addition
// * we force the addition to be carried out in double to
// * avoid rounding error in the comparison.
// *
// * See bug 4320890 for problems that this inaccuracy causes.
// */
// int out = 0;
// if (this.width <= 0) {
// out |= OUT_LEFT | OUT_RIGHT;
// } else if (x < this.x) {
// out |= OUT_LEFT;
// } else if (x > this.x + (double) this.width) {
// out |= OUT_RIGHT;
// }
// if (this.height <= 0) {
// out |= OUT_TOP | OUT_BOTTOM;
// } else if (y < this.y) {
// out |= OUT_TOP;
// } else if (y > this.y + (double) this.height) {
// out |= OUT_BOTTOM;
// }
// return out;
// }
// from http://shamimkhaliq.50megs.com/Java/lineclipper.htm
// private static int outCodes(Point P)
// {
// int Code = 0;
//
// if(P.y > yTop) Code += 1; /* code for above */
// else if(P.y < yBottom) Code += 2; /* code for below */
//
// if(P.x > xRight) Code += 4; /* code for right */
// else if(P.x < xLeft) Code += 8; /* code for left */
//
// return Code;
// }
//
// private static boolean rejectCheck(int outCode1, int outCode2)
// {
// if ((outCode1 & outCode2) != 0 ) return true;
// return(false);
// }
//
//
// private static boolean acceptCheck(int outCode1, int outCode2)
// {
// if ( (outCode1 == 0) && (outCode2 == 0) ) return(true);
// return(false);
// }
//
// static boolean CohenSutherland2DClipper(Point P0,Point P1)
// {
// int outCode0,outCode1;
// while(true)
// {
// outCode0 = outCodes(P0);
// outCode1 = outCodes(P1);
// if( rejectCheck(outCode0,outCode1) ) return(false);
// if( acceptCheck(outCode0,outCode1) ) return(true);
// if(outCode0 == 0)
// {
// double tempCoord; int tempCode;
// tempCoord = P0.x; P0.x= P1.x; P1.x = tempCoord;
// tempCoord = P0.y; P0.y= P1.y; P1.y = tempCoord;
// tempCode = outCode0; outCode0 = outCode1; outCode1 = tempCode;
// }
// if( (outCode0 & 1) != 0 )
// {
// P0.x += (P1.x - P0.x)*(yTop - P0.y)/(P1.y - P0.y);
// P0.y = yTop;
// }
// else
// if( (outCode0 & 2) != 0 )
// {
// P0.x += (P1.x - P0.x)*(yBottom - P0.y)/(P1.y - P0.y);
// P0.y = yBottom;
// }
// else
// if( (outCode0 & 4) != 0 )
// {
// P0.y += (P1.y - P0.y)*(xRight - P0.x)/(P1.x - P0.x);
// P0.x = xRight;
// }
// else
// if( (outCode0 & 8) != 0 )
// {
// P0.y += (P1.y - P0.y)*(xLeft - P0.x)/(P1.x - P0.x);
// P0.x = xLeft;
// }
// }
// }
}

View File

@ -0,0 +1,118 @@
/*
* 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.utils;
import android.graphics.Point;
// from http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
public class LineClipper {
private static final int INSIDE = 0; // 0000
private static int LEFT = 1; // 0001
private static int RIGHT = 2; // 0010
private static int BOTTOM = 4; // 0100
private static int TOP = 8; // 1000
// Compute the bit code for a point (x, y) using the clip rectangle
// bounded diagonally by (xmin, ymin), and (xmax, ymax)
private int xmin, xmax, ymin, ymax;
public boolean clip(Point p1, Point p2, int minx, int miny, int maxx, int maxy) {
this.xmin = minx;
this.ymin = miny;
this.xmax = maxx;
this.ymax = maxy;
return cohenSutherlandLineClip(p1.x, p1.y, p2.x, p2.y);
}
private int outCode(int x, int y) {
int code;
code = INSIDE; // initialised as being inside of clip window
if (x < xmin) // to the left of clip window
code |= LEFT;
else if (x > xmax) // to the right of clip window
code |= RIGHT;
if (y < ymin) // below the clip window
code |= BOTTOM;
else if (y > ymax) // above the clip window
code |= TOP;
return code;
}
// CohenSutherland clipping algorithm clips a line from
// P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with
// diagonal from (xmin, ymin) to (xmax, ymax).
private boolean cohenSutherlandLineClip(int x0, int y0, int x1, int y1) {
// compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
int outcode0 = outCode(x0, y0);
int outcode1 = outCode(x1, y1);
boolean accept = false;
while (true) {
if ((outcode0 | outcode1) == 0) { // Bitwise OR is 0. Trivially accept and get out of loop
accept = true;
break;
} else if ((outcode0 & outcode1) != 0) { // Bitwise AND is not 0. Trivially reject and get out of loop
break;
} else {
// failed both tests, so calculate the line segment to clip
// from an outside point to an intersection with clip edge
int x = 0;
int y = 0;
// At least one endpoint is outside the clip rectangle; pick it.
int outcodeOut = (outcode0 == 0) ? outcode1 : outcode0;
// Now find the intersection point;
// use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0)
if ((outcodeOut & TOP) != 0) { // point is above the clip rectangle
x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0);
y = ymax;
} else if ((outcodeOut & BOTTOM) != 0) { // point is below the clip rectangle
x = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0);
y = ymin;
} else if ((outcodeOut & RIGHT) != 0) { // point is to the right of clip rectangle
y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0);
x = xmax;
} else if ((outcodeOut & LEFT) != 0) { // point is to the left of clip rectangle
y = y0 + (y1 - y0) * (xmin - x0) / (x1 - x0);
x = xmin;
}
// Now we move outside point to intersection point to clip
// and get ready for next pass.
if (outcodeOut == outcode0) {
x0 = x;
y0 = y;
outcode0 = outCode(x0, y0);
} else {
x1 = x;
y1 = y;
outcode1 = outCode(x1, y1);
}
}
}
// TODO do sth with the result x0...
return accept;
}
}

View File

@ -19,10 +19,12 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import org.oscim.core.BoundingBox;
import org.oscim.core.GeoPoint;
import org.oscim.core.MapPosition;
import org.oscim.core.Tile;
@ -34,6 +36,9 @@ import org.oscim.database.OpenResult;
import org.oscim.generator.JobQueue;
import org.oscim.generator.JobTile;
import org.oscim.generator.MapWorker;
import org.oscim.overlay.LabelingOverlay;
import org.oscim.overlay.Overlay;
import org.oscim.overlay.OverlayManager;
import org.oscim.renderer.GLRenderer;
import org.oscim.renderer.GLView;
import org.oscim.renderer.TileGenerator;
@ -43,29 +48,28 @@ import org.oscim.theme.InternalRenderTheme;
import org.oscim.theme.RenderTheme;
import org.oscim.theme.RenderThemeHandler;
import org.oscim.theme.Theme;
import org.oscim.utils.AndroidUtils;
import org.xml.sax.SAXException;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
/**
* A MapView shows a map on the display of the device. It handles all user input
* and touch gestures to move and zoom the map.
*/
public class MapView extends FrameLayout {
public class MapView extends RelativeLayout {
final static String TAG = "MapView";
public static final boolean debugFrameTime = false;
public static final boolean testRegionZoom = false;
// public static final boolean staticLabeling = false;
private static final boolean debugDatabase = false;
RegionLookup mRegionLookup;
// RegionLookup mRegionLookup;
public boolean enableRotation = false;
public boolean enableCompass = false;
@ -81,10 +85,15 @@ public class MapView extends FrameLayout {
private MapDatabases mMapDatabaseType;
private TileManager mTileManager;
private GLView mGLView;
private JobQueue mJobQueue;
private MapWorker mMapWorkers[];
private int mNumMapWorkers = 4;
private final OverlayManager mOverlayManager;
private final GLView mGLView;
private final JobQueue mJobQueue;
// TODO use 1 download and 1 generator thread instead
private final MapWorker mMapWorkers[];
private final int mNumMapWorkers = 4;
private DebugSettings debugSettings;
private String mRenderTheme;
private Map<String, String> mMapOptions;
@ -124,8 +133,10 @@ public class MapView extends FrameLayout {
}
Log.d(TAG, "create MapView: " + mapDatabaseType.name());
// this.setDrawingCacheEnabled(true);
this.setWillNotDraw(true);
// TODO make this dpi dependent
// TODO set tilesize, make this dpi dependent
Tile.TILE_SIZE = 400;
MapActivity mapActivity = (MapActivity) context;
@ -138,6 +149,8 @@ public class MapView extends FrameLayout {
mMapViewPosition = new MapViewPosition(this);
mOverlayManager = new OverlayManager();
mTouchEventHandler = new TouchHandler(mapActivity, this);
mCompass = new Compass(mapActivity, this);
@ -145,6 +158,7 @@ public class MapView extends FrameLayout {
mJobQueue = new JobQueue();
mTileManager = TileManager.create(this);
mGLView = new GLView(context, this);
mMapWorkers = new MapWorker[mNumMapWorkers];
@ -156,7 +170,7 @@ public class MapView extends FrameLayout {
// .createMapDatabase(MapDatabases.TEST_READER);
mapDatabase = MapDatabaseFactory
.createMapDatabase(MapDatabases.MAP_READER);
mNumMapWorkers = 1;
// mNumMapWorkers = 1;
} else {
mapDatabase = MapDatabaseFactory.createMapDatabase(mapDatabaseType);
}
@ -187,8 +201,8 @@ public class MapView extends FrameLayout {
addView(mGLView, params);
if (testRegionZoom)
mRegionLookup = new RegionLookup(this);
// if (testRegionZoom)
// mRegionLookup = new RegionLookup(this);
mMapZoomControls = new MapZoomControls(mapActivity, this);
mMapZoomControls.setShowMapZoomControls(true);
@ -197,6 +211,35 @@ public class MapView extends FrameLayout {
for (MapWorker worker : mMapWorkers)
worker.start();
mOverlayManager.add(new LabelingOverlay(this));
// mOverlayManager.add(new GenericOverlay(this, new OverlayGrid(this)));
// mOverlayManager.add(new GenericOverlay(this, new OverlayTest(this)));
// ArrayList<OverlayItem> pList = new ArrayList<OverlayItem>();
// pList.add(new OverlayItem("title", "description", new GeoPoint(53.067221, 8.78767)));
// Overlay overlay = new ItemizedIconOverlay<OverlayItem>(this, context, pList, null);
// mOverlayManager.add(overlay);
// ArrayList<OverlayItem> pList = new ArrayList<OverlayItem>();
// pList.add(new ExtendedOverlayItem("Bremen", "description",
// new GeoPoint(53.047221, 8.78767), context));
// pList.add(new ExtendedOverlayItem("New York", "description",
// new GeoPoint(40.4251, -74.021), context));
// pList.add(new ExtendedOverlayItem("Tokyo", "description",
// new GeoPoint(35.4122, 139.4130), context));
// Overlay overlay = new ItemizedOverlayWithBubble<OverlayItem>(this, context, pList, null);
// mOverlayManager.add(overlay);
// PathOverlay pathOverlay = new PathOverlay(this, Color.BLUE, context);
// pathOverlay.addGreatCircle(
// new GeoPoint(53.047221, 8.78767),
// new GeoPoint(40.4251, -74.021));
// // pathOverlay.addPoint(new GeoPoint(53.047221, 8.78767));
// // pathOverlay.addPoint(new GeoPoint(53.067221, 8.78767));
// mOverlayManager.add(pathOverlay);
mMapViewPosition.animateTo(new GeoPoint(53.067221, 8.78767));
}
public void render() {
@ -259,6 +302,9 @@ public class MapView extends FrameLayout {
if (mPausing || this.getWidth() == 0 || this.getHeight() == 0)
return;
if (AndroidUtils.currentThreadIsUiThread())
mOverlayManager.onUpdate(mMapViewPosition.getMapPosition());
mTileManager.updateMap(false);
}
@ -266,7 +312,8 @@ public class MapView extends FrameLayout {
if (mPausing || this.getWidth() == 0 || this.getHeight() == 0)
return;
mTileManager.updateMap(true);
if (AndroidUtils.currentThreadIsUiThread())
mTileManager.updateMap(true);
}
/**
@ -291,7 +338,6 @@ public class MapView extends FrameLayout {
/**
* Sets the map file for this MapView.
*
* @param mapOptions
* ...
* @return true if the map file was set correctly, false otherwise.
@ -356,7 +402,6 @@ public class MapView extends FrameLayout {
/**
* Sets the MapDatabase for this MapView.
*
* @param mapDatabaseType
* the new MapDatabase.
*/
@ -396,7 +441,6 @@ public class MapView extends FrameLayout {
/**
* Sets the internal theme which is used for rendering the map.
*
* @param internalRenderTheme
* the internal rendering theme.
* @return ...
@ -418,7 +462,6 @@ public class MapView extends FrameLayout {
/**
* Sets the theme file which is used for rendering the map.
*
* @param renderThemePath
* the path to the XML file which defines the rendering theme.
* @throws IllegalArgumentException
@ -488,11 +531,11 @@ public class MapView extends FrameLayout {
mapWorkersProceed();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mMapZoomControls.onLayout(changed, left, top, right, bottom);
}
// @Override
// protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// // super.onLayout(changed, left, top, right, bottom);
// mMapZoomControls.onLayout(changed, left, top, right, bottom);
// }
void destroy() {
for (MapWorker mapWorker : mMapWorkers) {
@ -577,7 +620,6 @@ public class MapView extends FrameLayout {
/**
* Sets the center and zoom level of this MapView and triggers a redraw.
*
* @param mapPosition
* the new map position of this MapView.
*/
@ -591,7 +633,6 @@ public class MapView extends FrameLayout {
/**
* Sets the center of the MapView and triggers a redraw.
*
* @param geoPoint
* the new center point of the map.
*/
@ -611,7 +652,6 @@ public class MapView extends FrameLayout {
/**
* add jobs and remember MapWorkers that stuff needs to be done
*
* @param jobs
* tile jobs
*/
@ -648,6 +688,35 @@ public class MapView extends FrameLayout {
mapWorker.proceed();
}
/**
* You can add/remove/reorder your Overlays using the List of
* {@link Overlay}. The first (index 0) Overlay gets drawn first, the one
* with the highest as the last one.
* @return ...
*/
public List<Overlay> getOverlays() {
return this.getOverlayManager();
}
public OverlayManager getOverlayManager() {
return mOverlayManager;
}
public BoundingBox getBoundingBox() {
return mMapViewPosition.getViewBox();
}
// @Override
// protected void onLayout(boolean changed, int l, int t, int r, int b) {
// // TODO Auto-generated method stub
//
// }
//
// @Override
// protected void onMeasure()) {
// // TODO Auto-generated method stub
//
// }
// /**
// * Sets the visibility of the zoom controls.
// *

View File

@ -1,5 +1,6 @@
/*
* Copyright 2010, 2011, 2012 mapsforge.org, 2012 Hannes Janetzek
* 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
@ -14,30 +15,35 @@
*/
package org.oscim.view;
import java.lang.ref.WeakReference;
import org.oscim.core.BoundingBox;
import org.oscim.core.GeoPoint;
import org.oscim.core.MapPosition;
import org.oscim.core.MercatorProjection;
import org.oscim.utils.FastMath;
import android.graphics.Point;
import android.opengl.Matrix;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.FloatMath;
import android.util.Log;
/**
* A MapPosition stores the latitude and longitude coordinate of a MapView
* together with its zoom level.
* together with its zoom level, rotation and tilt
*/
// TODO use global coordinates that directly scale to pixel
public class MapViewPosition {
private static final String TAG = "MapViewPosition";
private static final String TAG = MapViewPosition.class.getSimpleName();
public final static int MAX_ZOOMLEVEL = 17;
public final static int MIN_ZOOMLEVEL = 2;
private final static float MAX_ANGLE = 35;
private final static float MAX_ANGLE = 40;
private final MapView mMapView;
@ -51,6 +57,12 @@ public class MapViewPosition {
private float mRotation;
public float mTilt;
// private static final int REF_ZOOM = 20;
private double mPosX;
private double mPosY;
private AnimationHandler mHandler;
MapViewPosition(MapView mapView) {
mMapView = mapView;
mLatitude = Double.NaN;
@ -60,6 +72,8 @@ public class MapViewPosition {
mRotation = 0.0f;
mTilt = 0;
mMapScale = 1;
mHandler = new AnimationHandler(this);
}
private float[] mProjMatrix = new float[16];
@ -69,19 +83,12 @@ public class MapViewPosition {
private float[] mRotMatrix = new float[16];
private float[] mTmpMatrix = new float[16];
private int mHeight, mWidth;
private static int mHeight, mWidth;
public final static float VIEW_SCALE = 1f / 2;
public final static float VIEW_DISTANCE = 2;
public final static float VIEW_NEAR = VIEW_DISTANCE;
public final static float VIEW_FAR = VIEW_DISTANCE * 2;
public final static float DIST = 2;
// public final static float VIEW_SCALE = 1f / 2;
// public final static float VIEW_DISTANCE = 1;
// public final static float VIEW_NEAR = 1;
// public final static float VIEW_FAR = 2;
void setViewport(int width, int height) {
float sw = VIEW_SCALE;
float sh = VIEW_SCALE;
@ -124,50 +131,211 @@ public class MapViewPosition {
mapPosition.scale = mScale;
mapPosition.zoomLevel = z;
mapPosition.x = MercatorProjection.longitudeToPixelX(mLongitude, z);
mapPosition.y = MercatorProjection.latitudeToPixelY(mLatitude, z);
mapPosition.x = mPosX;
mapPosition.y = mPosY;
if (mapPosition.viewMatrix != null)
System.arraycopy(mViewMatrix, 0, mapPosition.viewMatrix, 0, 16);
if (mapPosition.rotateMatrix != null)
System.arraycopy(mRotMatrix, 0, mapPosition.rotateMatrix, 0, 16);
if (coords == null)
return true;
// not so sure about this, but somehow works. weird z-values...
float tilt = FloatMath.sin((float) Math.toRadians(mTilt
// * 2.2f for dist = 1
* 1.4f // for dist = 2
// * 0.8f for dist = 4
* ((float) mHeight / mWidth)));
float tilt = getZ(1);
float d = 1f;
unproject(-d, d, tilt, coords, 0); // bottom-left
unproject(d, d, tilt, coords, 2); // bottom-right
unproject(d, -d, -tilt, coords, 4); // top-right
unproject(-d, -d, -tilt, coords, 6); // top-left
unproject(-1, 1, tilt, coords, 0); // bottom-left
unproject(1, 1, tilt, coords, 2); // bottom-right
unproject(1, -1, -tilt, coords, 4); // top-right
unproject(-1, -1, -tilt, coords, 6); // top-left
return true;
}
/** @return the current center point of the MapView. */
public synchronized GeoPoint getMapCenter() {
return new GeoPoint(mLatitude, mLongitude);
}
/**
* @return a MapPosition or null, if this map position is not valid.
* @see #isValid()
*/
public synchronized MapPosition getMapPosition() {
if (!isValid()) {
return null;
}
return new MapPosition(mLatitude, mLongitude, mZoomLevel, mScale, mRotation);
}
/** @return the current zoom level of the MapView. */
public synchronized byte getZoomLevel() {
return mZoomLevel;
}
/** @return the current scale of the MapView. */
public synchronized float getScale() {
return mScale;
}
/**
* ...
* @return BoundingBox containing view
*/
public synchronized BoundingBox getViewBox() {
float[] coords = mBBoxCoords;
float tilt = getZ(1);
unproject(-1, 1, -tilt, coords, 0); // top-left
unproject(1, 1, -tilt, coords, 2); // top-right
unproject(1, -1, tilt, coords, 4); // bottom-right
unproject(-1, -1, tilt, coords, 6); // bottom-left
byte z = mZoomLevel;
double dx, dy;
double minLat = 0, minLon = 0, maxLat = 0, maxLon = 0, lon, lat;
for (int i = 0; i < 8; i += 2) {
dx = mPosX - coords[i + 0] / mScale;
dy = mPosY - coords[i + 1] / mScale;
lon = MercatorProjection.pixelXToLongitude(dx, z);
lat = MercatorProjection.pixelYToLatitude(dy, z);
if (i == 0) {
minLon = maxLon = lon;
minLat = maxLat = lat;
} else {
if (lat > maxLat)
maxLat = lat;
else if (lat < minLat)
minLat = lat;
if (lon > maxLon)
maxLon = lon;
else if (lon < minLon)
minLon = lon;
}
}
return new BoundingBox(minLat, minLon, maxLat, maxLon);
}
private float[] mv = { 0, 0, 0, 1 };
// private float[] mu = { 0, 0, 0, 1 };
private float[] mu = { 0, 0, 0, 1 };
private float[] mBBoxCoords = new float[8];
private void unproject(float x, float y, float z, float[] coords, int position) {
// mv[0] = x;
// mv[1] = y;
// mv[2] = z - 2f;
// // mv[2] = 1f / (z - 2f);
// mv[3] = 1;
// Matrix.multiplyMV(mu, 0, mProjMatrix, 0, mv, 0);
/* get the depth-value of the map for the current tilt, approximately.
* needed to un-project a point on screen to the position on the map. not
* so sure about this, but at least somehow works. */
private float getZ(float y) {
return FloatMath.sin((float) Math.toRadians(mTilt))
// * 2.2f // for dist = 1
* 1.3f // for dist = 2
// * 0.8f // for dist = 4
* ((float) mHeight / mWidth) * y;
}
/**
* for x,y in screen coordinates get the point on the map in map-tile
* coordinates
* @param x ...
* @param y ...
* @param reuse ...
* @return ...
*/
public synchronized Point getScreenPointOnMap(float x, float y, Point reuse) {
Point out = reuse == null ? new Point() : reuse;
float mx = ((mWidth / 2) - x) / (mWidth / 2);
float my = ((mHeight / 2) - y) / (mHeight / 2);
unproject(-mx, my, getZ(my), mu, 0);
out.x = (int) (mPosX + mu[0] / mScale);
out.y = (int) (mPosY + mu[1] / mScale);
return out;
}
/**
* get the GeoPoint for x,y in screen coordinates
* @param x screen pixel x
* @param y screen pixel y
* @return the corresponding GeoPoint
*/
public synchronized GeoPoint fromScreenPixels(float x, float y) {
float mx = ((mWidth / 2) - x) / (mWidth / 2);
float my = ((mHeight / 2) - y) / (mHeight / 2);
unproject(-mx, my, getZ(my), mu, 0);
double dx = mPosX + mu[0] / mScale;
double dy = mPosY + mu[1] / mScale;
GeoPoint p = new GeoPoint(
MercatorProjection.pixelYToLatitude(dy, mZoomLevel),
MercatorProjection.pixelXToLongitude(dx, mZoomLevel));
// Log.d(">>>", "fromScreenPixels " + p);
return p;
}
/**
* get the screen pixel for a GeoPoint
* @param geoPoint ...
* @param reuse ...
* @return ...
*/
public synchronized Point project(GeoPoint geoPoint, Point reuse) {
Point out = reuse == null ? new Point() : reuse;
double x = MercatorProjection.longitudeToPixelX(geoPoint.getLongitude(),
mZoomLevel);
double y = MercatorProjection.latitudeToPixelY(geoPoint.getLatitude(),
mZoomLevel);
mv[0] = (float) (x - mPosX) * mScale;
mv[1] = (float) (y - mPosY) * mScale;
mv[2] = 0;
mv[3] = 1;
Matrix.multiplyMV(mv, 0, mViewMatrix, 0, mv, 0);
Matrix.multiplyMV(mv, 0, mProjMatrix, 0, mv, 0);
out.x = (int) (mv[0] / mv[3] * mWidth / 2);
out.y = (int) (mv[1] / mv[3] * mHeight / 2);
// Log.d(">>>", "project: " + out.x + " " + out.y);
return out;
}
public synchronized void getMVP(float[] matrix) {
Matrix.multiplyMM(matrix, 0, mProjMatrix, 0, mViewMatrix, 0);
}
// public static Point project(float x, float y, float[] matrix, float[] tmpVec, Point reuse) {
// Point out = reuse == null ? new Point() : reuse;
//
// tmpVec[0] = x;
// tmpVec[1] = y;
// tmpVec[2] = 0;
// tmpVec[3] = 1;
//
// Matrix.multiplyMV(tmpVec, 0, matrix, 0, tmpVec, 0);
//
// out.x = (int) (tmpVec[0] / tmpVec[3] * mWidth / 2);
// out.y = (int) (tmpVec[1] / tmpVec[3] * mHeight / 2);
//
// return out;
// }
private void unproject(float x, float y, float z, float[] coords, int position) {
mv[0] = x;
mv[1] = y;
mv[2] = z - 1f;
// mv[2] = -mu[2] / mu[3];
mv[3] = 1;
Matrix.multiplyMV(mv, 0, mUnprojMatrix, 0, mv, 0);
@ -175,17 +343,13 @@ public class MapViewPosition {
if (mv[3] != 0) {
coords[position] = mv[0] / mv[3];
coords[position + 1] = mv[1] / mv[3];
// Log.d(TAG, (z * 1.4f - 1) + " " + mu[2] / mu[3] + " - " + x + ":"
// + y + " - "
// + coords[position] + ":" + coords[position + 1] + " - " + mTilt);
} else {
// else what?
Log.d(TAG, "... what?");
Log.d(TAG, "uproject failed");
}
}
private void updateMatrix() {
Matrix.setRotateM(mRotMatrix, 0, mRotation, 0, 0, 1);
// - view matrix
// 1. scale to window coordinates
// 2. rotate
@ -195,11 +359,13 @@ public class MapViewPosition {
// 4. translate to near-plane
// 5. apply projection
Matrix.setRotateM(mRotMatrix, 0, mRotation, 0, 0, 1);
// tilt map
float tilt = mTilt;
Matrix.setRotateM(mTmpMatrix, 0, tilt, 1, 0, 0);
// apply first viewMatrix, then tilt
// apply first rotation, then tilt
Matrix.multiplyMM(mRotMatrix, 0, mTmpMatrix, 0, mRotMatrix, 0);
// scale to window coordinates
@ -231,91 +397,7 @@ public class MapViewPosition {
Matrix.multiplyMM(mUnprojMatrix, 0, mTmpMatrix, 0, mProjMatrixI, 0);
}
/**
* sets viewBox to visible bounding box, (left,top,right,bottom)
*
* @param viewBox
* ...
*/
public synchronized void getViewBox(final float[] viewBox) {
// updateMatrix();
float tilt = FloatMath.sin((float) Math.toRadians(mTilt)) * 4;
unproject(-1, 1, -tilt, mBBoxCoords, 0); // top-left
unproject(1, 1, -tilt, mBBoxCoords, 2); // top-right
unproject(1, -1, tilt, mBBoxCoords, 4); // bottom-right
unproject(-1, -1, tilt, mBBoxCoords, 6); // bottom-left
byte z = mZoomLevel;
double pixelX = MercatorProjection.longitudeToPixelX(mLongitude, z);
double pixelY = MercatorProjection.latitudeToPixelY(mLatitude, z);
double dx = pixelX - mBBoxCoords[0] / mScale;
double dy = pixelY - mBBoxCoords[1] / mScale;
double lon = MercatorProjection.pixelXToLongitude(dx, z);
double lat = MercatorProjection.pixelYToLatitude(dy, z);
Log.d(">>>", "bl:" + lon + " " + lat);
dx = pixelX - mBBoxCoords[2] / mScale;
dy = pixelY - mBBoxCoords[3] / mScale;
lon = MercatorProjection.pixelXToLongitude(dx, z);
lat = MercatorProjection.pixelYToLatitude(dy, z);
Log.d("...", "br:" + lon + " " + lat);
dx = pixelX - mBBoxCoords[4] / mScale;
dy = pixelY - mBBoxCoords[5] / mScale;
lon = MercatorProjection.pixelXToLongitude(dx, z);
lat = MercatorProjection.pixelYToLatitude(dy, z);
Log.d("...", "tl:" + lon + " " + lat);
dx = pixelX - mBBoxCoords[6] / mScale;
dy = pixelY - mBBoxCoords[7] / mScale;
lon = MercatorProjection.pixelXToLongitude(dx, z);
lat = MercatorProjection.pixelYToLatitude(dy, z);
Log.d("...", "tr:" + lon + " " + lat);
}
/**
* @return the current center point of the MapView.
*/
public synchronized GeoPoint getMapCenter() {
return new GeoPoint(mLatitude, mLongitude);
}
/**
* @return an immutable MapPosition or null, if this map position is not
* valid.
* @see #isValid()
*/
public synchronized MapPosition getMapPosition() {
if (!isValid()) {
return null;
}
// Log.d("MapViewPosition", "lat: " + mLatitude + " lon: " +
// mLongitude);
return new MapPosition(mLatitude, mLongitude, mZoomLevel, mScale, mRotation);
}
/**
* @return the current zoom level of the MapView.
*/
public synchronized byte getZoomLevel() {
return mZoomLevel;
}
/**
* @return the current scale of the MapView.
*/
public synchronized float getScale() {
return mScale;
}
/**
* @return true if this MapViewPosition is valid, false otherwise.
*/
/** @return true if this MapViewPosition is valid, false otherwise. */
public synchronized boolean isValid() {
if (Double.isNaN(mLatitude)) {
return false;
@ -336,168 +418,43 @@ public class MapViewPosition {
return true;
}
/**
* Get GeoPoint for a pixel on screen
*
* @param x
* ...
* @param y
* ...
* @return the GeoPoint
*/
public GeoPoint getOffsetPoint(float x, float y) {
double pixelX = MercatorProjection.longitudeToPixelX(mLongitude, mZoomLevel);
double pixelY = MercatorProjection.latitudeToPixelY(mLatitude, mZoomLevel);
double dx = ((mMapView.getWidth() >> 1) - x) / mScale;
double dy = ((mMapView.getHeight() >> 1) - y) / mScale;
if (mMapView.enableRotation || mMapView.enableCompass) {
double rad = Math.toRadians(mRotation);
double xx = dx * Math.cos(rad) + dy * -Math.sin(rad);
double yy = dx * Math.sin(rad) + dy * Math.cos(rad);
dx = pixelX - xx;
dy = pixelY - yy;
} else {
dx = pixelX - dx;
dy = pixelY - dy;
}
double latitude = MercatorProjection.pixelYToLatitude(dy, mZoomLevel);
latitude = MercatorProjection.limitLatitude(latitude);
double longitude = MercatorProjection.pixelXToLongitude(dx, mZoomLevel);
longitude = MercatorProjection.limitLongitude(longitude);
return new GeoPoint(latitude, longitude);
}
// public static double pixelXToLongitude(double pixelX, byte zoomLevel) {
// return 360 * ((pixelX / ((long) Tile.TILE_SIZE << zoomLevel)) - 0.5);
// }
//
// public static double pixelYToLatitude(double pixelY, byte zoomLevel) {
// double y = 0.5 - (pixelY / ((long) Tile.TILE_SIZE << zoomLevel));
// return 90 - 360 * Math.atan(Math.exp(-y * (2 * Math.PI))) / Math.PI;
// }
/**
* Moves this MapViewPosition by the given amount of pixels.
*
* @param mx
* the amount of pixels to move the map horizontally.
* @param my
* the amount of pixels to move the map vertically.
* @param mx the amount of pixels to move the map horizontally.
* @param my the amount of pixels to move the map vertically.
*/
public synchronized void moveMap(float mx, float my) {
double pixelX = MercatorProjection.longitudeToPixelX(mLongitude, mZoomLevel);
double pixelY = MercatorProjection.latitudeToPixelY(mLatitude, mZoomLevel);
double dx = mx / mScale;
double dy = my / mScale;
if (mMapView.enableRotation || mMapView.enableCompass) {
double rad = Math.toRadians(mRotation);
double x = dx * Math.cos(rad) + dy * Math.sin(rad);
double y = dx * -Math.sin(rad) + dy * Math.cos(rad);
double rcos = Math.cos(rad);
double rsin = Math.sin(rad);
double x = dx * rcos + dy * rsin;
double y = dx * -rsin + dy * rcos;
dx = x;
dy = y;
}
dx = pixelX - dx;
dy = pixelY - dy;
dx = mPosX - dx;
dy = mPosY - dy;
mLatitude = MercatorProjection.pixelYToLatitude(dy, mZoomLevel);
mLatitude = MercatorProjection.limitLatitude(mLatitude);
mLongitude = MercatorProjection.pixelXToLongitude(dx, mZoomLevel);
mLongitude = MercatorProjection.wrapLongitude(mLongitude);
// mLongitude = MercatorProjection.limitLongitude(mLongitude);
// getViewBox(null);
updatePosition();
}
public synchronized void rotateMap(float angle, float cx, float cy) {
moveMap(cx, cy);
// Log.d("MapViewPosition", "rotate:" + angle + " " + (mRotation -
// angle));
mRotation += angle;
updateMatrix();
}
public void setRotation(float f) {
mRotation = f;
updateMatrix();
}
public boolean tilt(float move) {
float tilt = mTilt + move;
if (tilt > MAX_ANGLE)
tilt = MAX_ANGLE;
else if (tilt < 0)
tilt = 0;
if (mTilt == tilt)
return false;
setTilt(tilt);
// mTilt = tilt;
// updateMatrix();
return true;
}
public void setTilt(float f) {
mTilt = f;
// float sw = VIEW_SCALE;
// float sh = VIEW_SCALE;
// sh += (mTilt / 250);
// Matrix.frustumM(mProjMatrix, 0, -sw * mWidth, sw * mWidth,
// sh * mHeight, -sh * mHeight, 1, 2);
//
// Matrix.translateM(mProjMatrix, 0, 0, 0, -VIEW_DISTANCE);
//
// Matrix.invertM(mProjMatrixI, 0, mProjMatrix, 0);
// Matrix.invertM(mUnprojMatrix, 0, mProjMatrix, 0);
updateMatrix();
}
synchronized void setMapCenter(GeoPoint geoPoint) {
mLatitude = MercatorProjection.limitLatitude(geoPoint.getLatitude());
mLongitude = MercatorProjection.limitLongitude(geoPoint.getLongitude());
}
synchronized void setMapCenter(MapPosition mapPosition) {
mLatitude = MercatorProjection.limitLatitude(mapPosition.lat);
mLongitude = MercatorProjection.limitLongitude(mapPosition.lon);
mZoomLevel = mMapView.limitZoomLevel(mapPosition.zoomLevel);
mMapScale = 1 << mZoomLevel;
}
synchronized void setZoomLevel(byte zoomLevel) {
mZoomLevel = mMapView.limitZoomLevel(zoomLevel);
mMapScale = 1 << mZoomLevel;
}
synchronized void setScale(float scale) {
mScale = scale;
}
// synchronized void zoomBoundingBox(GeoPoint p1, GeoPoint p2) {
//
// }
/**
* @param scale
* ...
* @param pivotX
* ...
* @param pivotY
* ...
* -
* @param scale ...
* @param pivotX ...
* @param pivotY ...
* @return true if scale was changed
*/
public synchronized boolean scaleMap(float scale, float pivotX, float pivotY) {
@ -512,13 +469,15 @@ public class MapViewPosition {
if (z > MAX_ZOOMLEVEL) {
// z17 shows everything, just increase scaling
// need to fix this for ScanBox
if (mScale * scale > 2) // 8)
if (mScale * scale > 4)
return false;
mScale *= scale;
mMapScale = newScale;
} else {
mZoomLevel = (byte) z;
updatePosition();
mScale = newScale / (1 << z);
mMapScale = newScale;
}
@ -530,4 +489,182 @@ public class MapViewPosition {
return true;
}
/**
* rotate map around pivot cx,cy
* @param angle ...
* @param cx ...
* @param cy ...
*/
public synchronized void rotateMap(float angle, float cx, float cy) {
moveMap(cx, cy);
mRotation += angle;
updateMatrix();
}
public synchronized void setRotation(float f) {
mRotation = f;
updateMatrix();
}
public synchronized boolean tilt(float move) {
float tilt = mTilt + move;
if (tilt > MAX_ANGLE)
tilt = MAX_ANGLE;
else if (tilt < 0)
tilt = 0;
if (mTilt == tilt)
return false;
setTilt(tilt);
return true;
}
public synchronized void setTilt(float f) {
mTilt = f;
updateMatrix();
}
private void setMapCenter(double latitude, double longitude) {
mLatitude = MercatorProjection.limitLatitude(latitude);
mLongitude = MercatorProjection.limitLongitude(longitude);
updatePosition();
}
synchronized void setMapCenter(GeoPoint geoPoint) {
setMapCenter(geoPoint.getLatitude(), geoPoint.getLongitude());
}
synchronized void setMapCenter(MapPosition mapPosition) {
mZoomLevel = mMapView.limitZoomLevel(mapPosition.zoomLevel);
mMapScale = 1 << mZoomLevel;
setMapCenter(mapPosition.lat, mapPosition.lon);
}
synchronized void setZoomLevel(byte zoomLevel) {
mZoomLevel = mMapView.limitZoomLevel(zoomLevel);
mMapScale = 1 << mZoomLevel;
updatePosition();
}
synchronized void setScale(float scale) {
mScale = scale;
}
private void updatePosition() {
mPosX = MercatorProjection.longitudeToPixelX(mLongitude, mZoomLevel);
mPosY = MercatorProjection.latitudeToPixelY(mLatitude, mZoomLevel);
}
private double mStartX;
private double mStartY;
private double mEndX;
private double mEndY;
private float mDuration = 500;
private Point mTmpPoint;
public synchronized void animateTo(GeoPoint geoPoint) {
MercatorProjection.projectPoint(geoPoint, mZoomLevel, mTmpPoint);
mEndX = MercatorProjection.longitudeToPixelX(geoPoint.getLongitude(), mZoomLevel);
mEndY = MercatorProjection.latitudeToPixelY(geoPoint.getLatitude(), mZoomLevel);
mStartX = mPosX;
mStartY = mPosY;
mDuration = 300;
mHandler.start((int) mDuration);
}
synchronized void setMapPosition(double x, double y) {
mLatitude = MercatorProjection.pixelYToLatitude(y, mZoomLevel);
mLatitude = MercatorProjection.limitLatitude(mLatitude);
mLongitude = MercatorProjection.pixelXToLongitude(x, mZoomLevel);
mLongitude = MercatorProjection.wrapLongitude(mLongitude);
updatePosition();
}
void onTick(long millisLeft) {
double adv = millisLeft / mDuration;
double mx = (mStartX + (mEndX - mStartX) * (1.0 - adv));
double my = (mStartY + (mEndY - mStartY) * (1.0 - adv));
setMapPosition(mx, my);
mMapView.redrawMap();
}
void onFinish() {
setMapPosition(mEndX, mEndY);
mMapView.redrawMap();
}
static class AnimationHandler extends Handler {
private final WeakReference<MapViewPosition> mMapViewPosition;
private static final int MSG = 1;
long mMillisInFuture;
long mInterval = 16;
long mStopTimeInFuture;
AnimationHandler(MapViewPosition mapAnimator) {
mMapViewPosition = new WeakReference<MapViewPosition>(mapAnimator);
}
public synchronized final void start(int millis) {
mMillisInFuture = millis;
MapViewPosition animator = mMapViewPosition.get();
if (animator == null)
return;
if (mMillisInFuture <= 0) {
animator.onFinish();
return;
}
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
removeMessages(MSG);
sendMessage(obtainMessage(MSG));
}
public final void cancel() {
removeMessages(MSG);
}
@Override
public void handleMessage(Message msg) {
MapViewPosition animator = mMapViewPosition.get();
if (animator == null)
return;
final long millisLeft = mStopTimeInFuture
- SystemClock.elapsedRealtime();
if (millisLeft <= 0) {
animator.onFinish();
} else if (millisLeft < mInterval) {
// no tick, just delay until done
sendMessageDelayed(obtainMessage(MSG), millisLeft);
} else {
long lastTickStart = SystemClock.elapsedRealtime();
animator.onTick(millisLeft);
// take into account user's onTick taking time to
// execute
long delay = lastTickStart + mInterval
- SystemClock.elapsedRealtime();
// special case: user's onTick took more than interval
// to
// complete, skip to next interval
while (delay < 0)
delay += mInterval;
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* 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

View File

@ -1,5 +1,6 @@
/*
* 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
@ -12,55 +13,52 @@
* 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.view;
import org.oscim.core.Tile;
import org.oscim.overlay.OverlayManager;
import android.content.Context;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ViewConfiguration;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.animation.DecelerateInterpolator;
import android.widget.Scroller;
/**
* Implementation for multi-touch capable devices.
*/
// TODO:
// - write a AnimationTimer instead of using CountDownTimers
// - fix recognition of tilt/rotate/scale state...
final class TouchHandler
extends SimpleOnGestureListener
implements ScaleGestureDetector.OnScaleGestureListener {
final class TouchHandler implements OnGestureListener, OnScaleGestureListener, OnDoubleTapListener {
private static final float SCALE_DURATION = 450;
private static final String TAG = TouchHandler.class.getSimpleName();
private static final float SCALE_DURATION = 500;
private static final float ROTATION_DELAY = 200; // ms
private static final int INVALID_POINTER_ID = -1;
private final MapView mMapView;
private final MapViewPosition mMapPosition;
private final DecelerateInterpolator mInterpolator = new DecelerateInterpolator();
private final OverlayManager mOverlayManager;
private final DecelerateInterpolator mInterpolator;
private final DecelerateInterpolator mLinearInterpolator;
private boolean mBeginScale;
private float mSumScale;
private final float mMapMoveDelta;
private boolean mMoveStart;
private boolean mBeginRotate;
private boolean mBeginTilt;
private boolean mLongPress;
// private long mLongPressTime;
private float mPosX;
// private float mPosX;
private float mPosY;
private double mAngle;
@ -76,16 +74,20 @@ final class TouchHandler
* the MapView
*/
public TouchHandler(Context context, MapView mapView) {
ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
mMapView = mapView;
mMapPosition = mapView.getMapPosition();
mMapMoveDelta = viewConfiguration.getScaledTouchSlop();
mOverlayManager = mapView.getOverlayManager();
// ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
// mMapMoveDelta = viewConfiguration.getScaledTouchSlop();
mActivePointerId = INVALID_POINTER_ID;
mScaleGestureDetector = new ScaleGestureDetector(context, this);
mGestureDetector = new GestureDetector(context, this);
mGestureDetector.setOnDoubleTapListener(this);
mScroller = new Scroller(mMapView.getContext(),
new android.view.animation.LinearInterpolator());
mInterpolator = new DecelerateInterpolator(1.5f);
mScroller = new Scroller(mMapView.getContext(), mInterpolator);
mLinearInterpolator = new DecelerateInterpolator(0.8f);//new android.view.animation.LinearInterpolator();
}
/**
@ -95,16 +97,8 @@ final class TouchHandler
*/
public boolean handleMotionEvent(MotionEvent event) {
// workaround for a bug in the ScaleGestureDetector, see Android issue
// #12976
// if (event.getAction() != MotionEvent.ACTION_MOVE
// || event.getPointerCount() > 1) {
mGestureDetector.onTouchEvent(event);
mScaleGestureDetector.onTouchEvent(event);
// }
if (!mScaling)
mGestureDetector.onTouchEvent(event);
int action = getAction(event);
boolean ret = false;
@ -136,10 +130,10 @@ final class TouchHandler
}
private boolean onActionDown(MotionEvent event) {
mPosX = event.getX();
// mPosX = event.getX();
mPosY = event.getY();
mMoveStart = false;
// mMoveStart = false;
mBeginRotate = false;
mBeginTilt = false;
// save the ID of the pointer
@ -154,43 +148,19 @@ final class TouchHandler
private boolean onActionMove(MotionEvent event) {
int id = event.findPointerIndex(mActivePointerId);
// calculate the distance between previous and current position
float moveX = event.getX(id) - mPosX;
float moveY = event.getY(id) - mPosY;
// save the position of the event
// Log.d("...", "mx " + moveX + " my " + moveY);
boolean scaling = mScaleGestureDetector.isInProgress();
if (!mScaling)
mScaling = scaling;
if (!scaling && !mMoveStart) {
if (Math.abs(moveX) > mMapMoveDelta || Math.abs(moveY) > mMapMoveDelta) {
// the map movement threshold has been reached
// longPressDetector.pressStop();
mMoveStart = true;
}
return true;
}
mPosX = event.getX(id);
mPosY = event.getY(id);
float py = event.getY(id);
float moveY = py - mPosY;
mPosY = py;
// double-tap + hold
if (mLongPress) {
mMapPosition.scaleMap(1 - moveY / 100, 0, 0);
mMapView.redrawMap();
return true;
}
if (multi == 0) {
mMapPosition.moveMap(moveX, moveY);
mMapView.redrawMap();
if (multi == 0)
return true;
}
if (event.getEventTime() - mMultiTouchDownTime < ROTATION_DELAY)
return true;
@ -206,18 +176,22 @@ final class TouchHandler
double rad = Math.atan2(dy, dx);
double r = rad - mAngle;
if (!mBeginRotate) {
if (!mBeginRotate && !mBeginScale) {
/* our naive gesture detector for rotation and tilt.. */
if (Math.abs(rad) < 0.25 || Math.abs(rad) > Math.PI - 0.25) {
mBeginTilt = true;
if (mMapPosition.tilt(moveY / 4)) {
mMapView.redrawMap();
}
return true;
}
if (!mBeginScale && !mBeginTilt) {
if (!mBeginTilt) {
if (Math.abs(r) > 0.05) {
Log.d("...", "begin rotate");
// Log.d(TAG, "begin rotate");
mAngle = rad;
mBeginRotate = true;
}
}
@ -246,7 +220,7 @@ final class TouchHandler
private long mMultiTouchDownTime;
private boolean onActionPointerDown(MotionEvent event) {
// longPressDetector.pressStop();
mMultiTouchDownTime = event.getEventTime();
multi++;
@ -256,7 +230,7 @@ final class TouchHandler
double dy = event.getY(0) - event.getY(1);
mAngle = Math.atan2(dy, dx);
}
Log.d("...", "multi down " + multi);
// Log.d("...", "multi down " + multi);
return true;
}
@ -274,14 +248,14 @@ final class TouchHandler
pointerIndex = 0;
}
// save the position of the event
mPosX = motionEvent.getX(pointerIndex);
// mPosX = motionEvent.getX(pointerIndex);
mPosY = motionEvent.getY(pointerIndex);
mActivePointerId = motionEvent.getPointerId(pointerIndex);
}
multi--;
mLongPress = false;
Log.d("...", "multi up " + multi);
// Log.d("...", "multi up " + multi);
return true;
}
@ -294,38 +268,32 @@ final class TouchHandler
private boolean onActionUp(MotionEvent motionEvent) {
mActivePointerId = INVALID_POINTER_ID;
mScaling = false;
multi = 0;
// if (mLongPress && SystemClock.uptimeMillis() - mLongPressTime < 150)
// {
// mScrollX = (mPosX - (mMapView.getWidth() >> 1)) * 2f;
// mScrollY = (mPosY - (mMapView.getHeight() >> 1)) * 2f;
// mPrevScale = 0;
//
// mTimer = new CountDownTimer((int) SCALE_DURATION, 30) {
// @Override
// public void onTick(long tick) {
// scale2(tick);
// }
//
// @Override
// public void onFinish() {
// scale2(0);
// }
// }.start();
// }
mLongPress = false;
multi = 0;
return true;
}
/******************* SimpleOnGestureListener *******************/
/******************* GestureListener *******************/
private Scroller mScroller;
private float mScrollX, mScrollY;
private boolean fling = false;
@Override
public void onShowPress(MotionEvent e) {
Log.d(TAG, "show press");
// TODO Auto-generated method stub
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.d(TAG, "single tap up");
return mOverlayManager.onSingleTapUp(e, mMapView);
}
@Override
public boolean onDown(MotionEvent e) {
if (fling) {
@ -338,6 +306,8 @@ final class TouchHandler
fling = false;
}
// Log.d(TAG, "tap");
return true;
}
@ -359,9 +329,32 @@ final class TouchHandler
return true;
}
@Override
public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX,
final float distanceY) {
if (mOverlayManager.onScroll(e1, e2, distanceX, distanceY, mMapView)) {
return true;
}
if (mScaling)
return true;
if (multi == 0) {
mMapPosition.moveMap(-distanceX, -distanceY);
mMapView.redrawMap();
}
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
if (mScaling)
return true;
int w = Tile.TILE_SIZE * 20;
int h = Tile.TILE_SIZE * 20;
mScrollX = 0;
@ -376,7 +369,7 @@ final class TouchHandler
-w, w, -h, h);
// animate for two seconds
mTimer = new CountDownTimer(1500, 50) {
mTimer = new CountDownTimer(1500, 16) {
@Override
public void onTick(long tick) {
scroll();
@ -384,20 +377,26 @@ final class TouchHandler
@Override
public void onFinish() {
// do nothing
}
}.start();
fling = true;
return true;
}
@Override
public void onLongPress(MotionEvent e) {
if (MapView.testRegionZoom) {
Log.d("mapsforge", "long press");
mMapView.mRegionLookup.updateRegion(-1, null);
if (mLongPress)
return;
if (mOverlayManager.onLongPress(e, mMapView)) {
return;
}
mLongPress = true;
// if (MapView.testRegionZoom) {
// Log.d("mapsforge", "long press");
// mMapView.mRegionLookup.updateRegion(-1, null);
// }
}
boolean scale2(long tick) {
@ -422,36 +421,30 @@ final class TouchHandler
return true;
}
/******************* DoubleTapListener ****************/
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// Log.d(TAG, "single tap confirmed");
return mOverlayManager.onSingleTapConfirmed(e, mMapView);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
if (MapView.testRegionZoom) {
mMapView.mRegionLookup.updateRegion(1,
mMapPosition.getOffsetPoint(mPosX, mPosY));
} else {
mLongPress = true;
Log.d(TAG, "double tap");
// mLongPressTime = SystemClock.uptimeMillis();
// mScrollX = (e.getX(0) - (mMapView.getWidth() >> 1)) * 2f;
// mScrollY = (e.getY(0) - (mMapView.getHeight() >> 1)) * 2f;
// mPrevScale = 0;
//
// mTimer = new CountDownTimer((int) SCALE_DURATION, 30) {
// @Override
// public void onTick(long tick) {
// scale2(tick);
// }
//
// @Override
// public void onFinish() {
// scale(0);
// }
// }.start();
}
return true;
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
mLongPress = true;
Log.d(TAG, "double tap event");
// TODO Auto-generated method stub
return false;
}
/******************* ScaleListener *******************/
private float mCenterX;
private float mCenterY;
private float mFocusX;
@ -475,7 +468,7 @@ final class TouchHandler
if (!mBeginScale) {
if (mSumScale > 1.1 || mSumScale < 0.9) {
Log.d("...", "begin scale " + mSumScale);
// Log.d("...", "begin scale " + mSumScale);
mBeginScale = true;
// scale = mSumScale;
}
@ -489,9 +482,11 @@ final class TouchHandler
@Override
public boolean onScaleBegin(ScaleGestureDetector gd) {
mScaling = true;
mBeginScale = false;
mTimeEnd = mTimeStart = SystemClock.elapsedRealtime();
mSumScale = 1;
mBeginScale = false;
mCenterX = mMapView.getWidth() >> 1;
mCenterY = mMapView.getHeight() >> 1;
@ -514,19 +509,21 @@ final class TouchHandler
mZooutOut = mSumScale < 0.99;
mTimer = new CountDownTimer((int) SCALE_DURATION, 15) {
mTimer = new CountDownTimer((int) SCALE_DURATION, 32) {
@Override
public void onTick(long tick) {
scale(tick);
scaleAnim(tick);
}
@Override
public void onFinish() {
scale(0);
scaleAnim(0);
}
}.start();
} else {
mScaling = false;
}
mBeginScale = false;
}
@ -534,7 +531,7 @@ final class TouchHandler
private CountDownTimer mTimer;
boolean mZooutOut;
boolean scale(long tick) {
boolean scaleAnim(long tick) {
if (mPrevScale >= 1) {
mTimer = null;
@ -542,7 +539,8 @@ final class TouchHandler
}
float adv = (SCALE_DURATION - tick) / SCALE_DURATION;
adv = mInterpolator.getInterpolation(adv);
// adv = mInterpolator.getInterpolation(adv);
adv = mLinearInterpolator.getInterpolation(adv);
float scale = adv - mPrevScale;
mPrevScale += scale;
@ -560,120 +558,4 @@ final class TouchHandler
return true;
}
/*
* from CountDownTimer.java: Copyright (C) 2008 The Android Open Source
* Project Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may
* obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
* law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*/
final static class Timer {
/**
* Millis since epoch when alarm should stop.
*/
private final long mMillisInFuture;
/**
* The interval in millis that the user receives callbacks
*/
final long mCountdownInterval;
long mStopTimeInFuture;
/**
* @param millisInFuture
* The number of millis in the future from the call to
* {@link #start()} until the countdown is done and
* {@link #onFinish()} is called.
* @param countDownInterval
* The interval along the way to receive
* {@link #onTick(long)} callbacks.
*/
public Timer(long millisInFuture, long countDownInterval) {
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
}
/**
* Cancel the countdown.
*/
public final void cancel() {
mHandler.removeMessages(MSG);
}
/**
* Start the countdown.
*
* @return ...
*/
public synchronized final Timer start() {
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
mHandler.sendMessage(mHandler.obtainMessage(MSG));
return this;
}
/**
* Callback fired on regular interval.
*
* @param millisUntilFinished
* The amount of time until finished.
*/
public void onTick(long millisUntilFinished) {
}
/**
* Callback fired when the time is up.
*/
public void onFinish() {
}
private static final int MSG = 1;
// handles counting down
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
synchronized (Timer.this) {
final long millisLeft = mStopTimeInFuture
- SystemClock.elapsedRealtime();
if (millisLeft <= 0) {
onFinish();
} else if (millisLeft < mCountdownInterval) {
// no tick, just delay until done
sendMessageDelayed(obtainMessage(MSG), millisLeft);
} else {
long lastTickStart = SystemClock.elapsedRealtime();
onTick(millisLeft);
// take into account user's onTick taking time to
// execute
long delay = lastTickStart + mCountdownInterval
- SystemClock.elapsedRealtime();
// special case: user's onTick took more than interval
// to
// complete, skip to next interval
while (delay < 0)
delay += mCountdownInterval;
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};
}
}

View File

@ -0,0 +1,135 @@
package org.osmdroid.location;
import java.util.ArrayList;
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 android.util.Log;
/**
* POI Provider using Flickr service to get geolocalized photos.
* @see "http://www.flickr.com/services/api/flickr.photos.search.html"
* @author M.Kergall
*/
public class FlickrPOIProvider {
protected String mApiKey;
/**
* @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.d(BonusPackHelper.LOG_TAG,
* "getPhoto:"+url); String jString =
* BonusPackHelper.requestStringFromUrl(url); if (jString == null) {
* Log.e(BonusPackHelper.LOG_TAG, "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.d(BonusPackHelper.LOG_TAG, "FlickrPOIProvider:get:" + fullUrl);
String jString = BonusPackHelper.requestStringFromUrl(fullUrl);
if (jString == null) {
Log.e(BonusPackHelper.LOG_TAG, "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");
POI poi = new POI(POI.POI_SERVICE_FLICKR);
poi.location = new GeoPoint(
jPhoto.getDouble("latitude"),
jPhoto.getDouble("longitude"));
poi.id = Long.parseLong(photoId);
poi.type = jPhoto.getString("title");
poi.thumbnailPath = jPhoto.getString("url_sq");
String owner = jPhoto.getString("owner");
poi.url = "http://www.flickr.com/photos/" + owner + "/" + photoId;
pois.add(poi);
}
int total = jPhotos.getInt("total");
Log.d(BonusPackHelper.LOG_TAG, "done:" + n + " got, 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, int maxResults) {
String url = getUrlInside(boundingBox, maxResults);
return getThem(url);
}
}

View File

@ -0,0 +1,219 @@
package org.osmdroid.location;
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;
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.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import android.util.Log;
/**
* POI Provider using GeoNames services. Currently, "find Nearby Wikipedia" and
* "Wikipedia Articles in Bounding Box" services.
* @see "http://www.geonames.org"
* @author M.Kergall
*/
public class GeoNamesPOIProvider {
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.d(BonusPackHelper.LOG_TAG, "GeoNamesPOIProvider:get:" + fullUrl);
String jString = BonusPackHelper.requestStringFromUrl(fullUrl);
if (jString == null) {
Log.e(BonusPackHelper.LOG_TAG, "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.d(BonusPackHelper.LOG_TAG, "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.d(BonusPackHelper.LOG_TAG, "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.d(BonusPackHelper.LOG_TAG, "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);
}
}
}

View File

@ -0,0 +1,215 @@
package org.osmdroid.location;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.osmdroid.utils.BonusPackHelper;
import android.content.Context;
import android.location.Address;
import android.util.Log;
/**
* 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 {
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.d(BonusPackHelper.LOG_TAG, "GeocoderNominatim::getFromLocation:" + url);
String result = BonusPackHelper.requestStringFromUrl(url);
//Log.d("NOMINATIM", 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);
if (lowerLeftLatitude != 0.0 && lowerLeftLongitude != 0.0) {
//viewbox = left, top, right, bottom:
url += "&viewbox=" + lowerLeftLongitude
+ "," + upperRightLatitude
+ "," + upperRightLongitude
+ "," + lowerLeftLatitude
+ "&bounded=1";
}
Log.d(BonusPackHelper.LOG_TAG, "GeocoderNominatim::getFromLocationName:" + url);
String result = BonusPackHelper.requestStringFromUrl(url);
//Log.d(BonusPackHelper.LOG_TAG, 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);
}
}

View File

@ -0,0 +1,184 @@
package org.osmdroid.location;
import java.net.URLEncoder;
import java.util.ArrayList;
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 android.graphics.Bitmap;
import android.util.Log;
/**
* 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 {
/*
* 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;
}
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.d(BonusPackHelper.LOG_TAG, "NominatimPOIProvider:get:" + url);
String jString = BonusPackHelper.requestStringFromUrl(url);
if (jString == null) {
Log.e(BonusPackHelper.LOG_TAG, "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.optLong("osm_id");
poi.location = new GeoPoint(jPlace.getDouble("lat"), jPlace.getDouble("lon"));
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);
}
/**
* @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());
}
}

View File

@ -0,0 +1,177 @@
package org.osmdroid.location;
import org.oscim.app.R;
import org.oscim.core.GeoPoint;
import org.osmdroid.utils.BonusPackHelper;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
/**
* Point of Interest. Exact content may depend of the POI provider used.
* @see NominatimPOIProvider
* @see GeoNamesPOIProvider
* @author M.Kergall
*/
public class POI implements Parcelable {
/** 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;
/** Identifies the service provider of this POI. */
public int serviceId;
/** Nominatim: OSM ID. GeoNames: 0 */
public long id;
/** location of the POI */
public GeoPoint location;
/** 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) {
Log.d(BonusPackHelper.LOG_TAG, "POI:load thumbnail:" + thumbnailPath);
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);
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 (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.writeLong(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.readLong();
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();
// }
}

View File

@ -0,0 +1,155 @@
package org.osmdroid.location;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.oscim.core.BoundingBox;
import org.oscim.core.GeoPoint;
import org.osmdroid.utils.BonusPackHelper;
import org.osmdroid.utils.HttpConnection;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import android.util.Log;
/** POI Provider using Picasa service.
* @see "https://developers.google.com/picasa-web/docs/2.0/reference"
* @author M.Kergall */
public class PicasaPOIProvider {
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;
}
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.d(BonusPackHelper.LOG_TAG, "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.d(BonusPackHelper.LOG_TAG, "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 ArrayList<POI> getPOIInside(BoundingBox boundingBox, int maxResults, String query) {
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 = Long.parseLong(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);
}
}
}

View File

@ -0,0 +1,100 @@
package org.osmdroid.overlays;
import org.oscim.view.MapView;
import org.osmdroid.utils.BonusPackHelper;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
/**
* 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();
mTitleId = context.getResources().getIdentifier("id/bubble_title", null, packageName);
mDescriptionId = context.getResources().getIdentifier("id/bubble_description", null,
packageName);
mSubDescriptionId = context.getResources().getIdentifier("id/bubble_subdescription", null,
packageName);
mImageId = context.getResources().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(mapView.getContext());
// 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(ExtendedOverlayItem 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
}
}

View File

@ -0,0 +1,168 @@
package org.osmdroid.overlays;
import org.oscim.core.GeoPoint;
import org.oscim.overlay.OverlayItem;
import org.oscim.view.MapView;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
/**
* 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>
* @see ItemizedOverlayWithBubble
* @author M.Kergall
*/
public class ExtendedOverlayItem extends OverlayItem {
// 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 ExtendedOverlayItem(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;
}
/**
* From a HotspotPlace and drawable dimensions (width, height), return the
* hotspot position. Could be a public method of HotspotPlace or
* OverlayItem...
* @param place
* ...
* @param w
* ...
* @param h
* ...
* @return ...
*/
public Point getHotspot(HotspotPlace place, int w, int h) {
Point hp = new Point();
if (place == null)
place = HotspotPlace.BOTTOM_CENTER; // use same default than in
// osmdroid.
switch (place) {
case NONE:
hp.set(0, 0);
break;
case BOTTOM_CENTER:
hp.set(w / 2, 0);
break;
case LOWER_LEFT_CORNER:
hp.set(0, 0);
break;
case LOWER_RIGHT_CORNER:
hp.set(w, 0);
break;
case CENTER:
hp.set(w / 2, -h / 2);
break;
case LEFT_CENTER:
hp.set(0, -h / 2);
break;
case RIGHT_CENTER:
hp.set(w, -h / 2);
break;
case TOP_CENTER:
hp.set(w / 2, -h);
break;
case UPPER_LEFT_CORNER:
hp.set(0, -h);
break;
case UPPER_RIGHT_CORNER:
hp.set(w, -h);
break;
}
return hp;
}
/**
* 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 mapView
* ...
*/
public void showBubble(InfoWindow bubble, MapView mapView) {
// 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);
}
}

View File

@ -0,0 +1,140 @@
package org.osmdroid.overlays;
// TODO composite view as texture overlay and only allow one bubble at a time.
import org.oscim.view.MapView;
import android.content.Context;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
/**
* 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.
* @see DefaultInfoWindow
* @author M.Kergall
*/
public abstract class InfoWindow {
protected View mView;
protected boolean mIsVisible = false;
protected MapView mMapView;
protected RelativeLayout mLayout;
private android.widget.RelativeLayout.LayoutParams mLayoutPos;
/**
* @param layoutResId
* the id of the view resource.
* @param mapView
* the mapview on which is hooked the view
*/
public InfoWindow(int layoutResId, MapView mapView) {
mMapView = mapView;
mIsVisible = false;
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.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
mLayout.setLayoutParams(rlp);
mLayoutPos = rlp;
// not so sure about this. why is just blitting the bitmap on glview so slow?...
mView.setDrawingCacheEnabled(true);
// mLayout.setDrawingCacheEnabled(true);
// mLayout.setPersistentDrawingCache(ViewGroup.PERSISTENT_ALL_CACHES);
// mLayout.setAlwaysDrawnWithCacheEnabled(true); // call this method
mLayout.setWillNotDraw(true);
mLayout.addView(mView);
}
/**
* Returns the Android view. This allows to set its content.
* @return the Android view
*/
public View getView() {
return (mView);
}
/**
* 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(ExtendedOverlayItem item, int offsetX, int offsetY) {
onOpen(item);
close(); // if it was already opened
// mView.requestLayout();
mView.buildDrawingCache();
mMapView.addView(mLayout);
mIsVisible = true;
}
public void position(int x, int y) {
// if this isnt madness...
RelativeLayout.LayoutParams rlp = mLayoutPos;
rlp.leftMargin = x;
rlp.rightMargin = -x;
rlp.topMargin = -y;
rlp.bottomMargin = y + mMapView.getHeight() / 2;
mLayout.setLayoutParams(rlp);
//mMapView.requestLayout();
mLayout.requestLayout();
// using scrollTo the bubble somehow does not appear when it
// is not already in viewport...
// mLayout.scrollTo(-x, y + mMapView.getHeight() / 2);
}
public void close() {
if (mIsVisible) {
mIsVisible = false;
((ViewGroup) mLayout.getParent()).removeView(mLayout);
onClose();
}
}
public boolean isOpen() {
return mIsVisible;
}
// Abstract methods to implement:
public abstract void onOpen(ExtendedOverlayItem item);
public abstract void onClose();
}

View File

@ -0,0 +1,199 @@
package org.osmdroid.overlays;
import java.util.List;
import org.oscim.core.GeoPoint;
import org.oscim.core.MapPosition;
import org.oscim.overlay.ItemizedIconOverlay;
import org.oscim.overlay.OverlayItem;
import org.oscim.view.MapView;
import org.osmdroid.utils.BonusPackHelper;
import android.content.Context;
import android.graphics.Point;
import android.util.Log;
/**
* 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 ExtendedOverlayItem. <br>
* @see ExtendedOverlayItem
* @see InfoWindow
* @author M.Kergall
* @param <Item>
* ...
*/
public class ItemizedOverlayWithBubble<Item extends OverlayItem> extends ItemizedIconOverlay<Item>
implements ItemizedIconOverlay.OnItemGestureListener<Item>
{
protected List<Item> mItemsList;
// 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 OverlayItem mItemWithBubble;
static int layoutResId = 0;
@Override
public boolean onItemLongPress(final int index, final OverlayItem item) {
if (mBubble.isOpen())
hideBubble();
else
showBubble(index);
return false;
}
@Override
public boolean onItemSingleTapUp(final int index, final OverlayItem item) {
showBubble(index);
return false;
}
private Point mTmpPoint = new Point();
@Override
public void onUpdate(MapPosition mapPosition) {
if (mBubble.isOpen()) {
GeoPoint gp = mItemWithBubble.getPoint();
Point p = mTmpPoint;
mMapView.getMapViewPosition().project(gp, p);
mBubble.position(p.x, p.y);
}
}
public ItemizedOverlayWithBubble(final MapView mapView, final Context context,
final List<Item> aList, final InfoWindow bubble) {
super(mapView, context, aList, null);
mItemsList = aList;
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, mapView);
}
mItemWithBubble = null;
mOnItemGestureListener = this;
}
public ItemizedOverlayWithBubble(final Context context, final List<Item> aList,
final MapView mapView) {
this(mapView, context, aList, null);
}
void showBubble(int index) {
showBubbleOnItem(index, mMapView);
}
/**
* 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
* @param mapView
* ...
*/
public void showBubbleOnItem(final int index, final MapView mapView) {
ExtendedOverlayItem eItem = (ExtendedOverlayItem) (getItem(index));
mItemWithBubble = eItem;
if (eItem != null) {
eItem.showBubble(mBubble, mapView);
// mMapView.getMapViewPosition().animateTo(eItem.mGeoPoint, 0);
mapView.redrawMap();
setFocus((Item) eItem);
}
}
/** Close the bubble (if it's opened). */
public void hideBubble() {
mBubble.close();
mItemWithBubble = null;
}
@Override
protected boolean onSingleTapUpHelper(final int index, final Item item, final MapView mapView) {
showBubbleOnItem(index, mapView);
return true;
}
/** @return the item currenty showing the bubble, or null if none. */
public OverlayItem 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() {
OverlayItem item = getBubbledItem();
if (item == null)
return -1;
return mItemsList.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();
}
// FIXME @Override
// public void draw(final Canvas canvas, final MapView mapView, final boolean shadow) {
// // 1. Fixing drawing focused item on top in ItemizedOverlay (osmdroid
// // issue 354):
// if (shadow) {
// return;
// }
// final Projection pj = mapView.getProjection();
// final int size = mItemsList.size() - 1;
// final Point mCurScreenCoords = new Point();
//
// /*
// * Draw in backward cycle, so the items with the least index are on the
// * front.
// */
// for (int i = size; i >= 0; i--) {
// final Item item = getItem(i);
// if (item != mItemWithBubble) {
// pj.toMapPixels(item.mGeoPoint, mCurScreenCoords);
// onDrawItem(canvas, item, mCurScreenCoords);
// }
// }
// // draw focused item last:
// if (mItemWithBubble != null) {
// pj.toMapPixels(mItemWithBubble.mGeoPoint, mCurScreenCoords);
// onDrawItem(canvas, (Item) mItemWithBubble, mCurScreenCoords);
// }
// }
}

View File

@ -0,0 +1,58 @@
package org.osmdroid.overlays;
import org.oscim.core.GeoPoint;
import org.oscim.overlay.Overlay;
import org.oscim.view.MapView;
import android.content.Context;
import android.view.MotionEvent;
/**
* Empty overlay than can be used to detect events on the map, and to throw them
* to a MapEventsReceiver.
*
* @see MapEventsReceiver
* @author M.Kergall
*/
public class MapEventsOverlay extends Overlay {
private MapEventsReceiver mReceiver;
/**
* @param ctx
* the context
* @param receiver
* the object that will receive/handle the events. It must
* implement MapEventsReceiver interface.
*/
public MapEventsOverlay(Context ctx, MapEventsReceiver receiver) {
super(ctx);
mReceiver = receiver;
}
// @Override
// protected void draw(Canvas c, MapView osmv, boolean shadow) {
// // Nothing to draw
// }
@Override
public boolean onSingleTapUp(MotionEvent e, MapView mapView) {
// Projection proj = mapView.getProjection();
// GeoPoint p = proj.fromPixels(e.getX(), e.getY());
GeoPoint p = mapView.getMapViewPosition().fromScreenPixels(e.getX(), e.getY());
return mReceiver.singleTapUpHelper(p);
}
@Override
public boolean onLongPress(MotionEvent e, MapView mapView) {
// Projection proj = mapView.getProjection();
// GeoPoint p = proj.fromPixels(e.getX(), e.getY());
GeoPoint p = mapView.getMapViewPosition().fromScreenPixels(e.getX(), e.getY());
// throw event to the receiver:
return mReceiver.longPressHelper(p);
}
}

Some files were not shown because too many files have changed in this diff Show More