diff --git a/anymaps-base/.gitignore b/anymaps-base/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/anymaps-base/.gitignore @@ -0,0 +1 @@ +/build diff --git a/anymaps-base/build.gradle b/anymaps-base/build.gradle new file mode 100644 index 0000000..f331c00 --- /dev/null +++ b/anymaps-base/build.gradle @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ +ext { + name = 'AnyMaps - Base Library' + artifactId = 'anymaps.base' + description = 'Base library for AnyMaps libraries. Contains common interface' +} + +apply plugin: 'com.android.library' +apply plugin: 'maven' + +android { + compileSdkVersion 28 + buildToolsVersion "28.0.3" + + defaultConfig { + minSdkVersion 15 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + } + } +} + +dependencies { + implementation 'com.android.support:support-annotations:28.0.0' + + testImplementation 'junit:junit:4.12' +} \ No newline at end of file diff --git a/anymaps-base/src/main/AndroidManifest.xml b/anymaps-base/src/main/AndroidManifest.xml new file mode 100644 index 0000000..79083f8 --- /dev/null +++ b/anymaps-base/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + diff --git a/anymaps-base/src/main/java/com/car2go/maps/AnyMap.java b/anymaps-base/src/main/java/com/car2go/maps/AnyMap.java new file mode 100644 index 0000000..1252173 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/AnyMap.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2017 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps; + +import android.view.View; + +import com.car2go.maps.model.CameraPosition; +import com.car2go.maps.model.Circle; +import com.car2go.maps.model.CircleOptions; +import com.car2go.maps.model.LatLng; +import com.car2go.maps.model.Marker; +import com.car2go.maps.model.MarkerOptions; +import com.car2go.maps.model.Polygon; +import com.car2go.maps.model.PolygonOptions; +import com.car2go.maps.model.Polyline; +import com.car2go.maps.model.PolylineOptions; + +/** + * Provider-independent map controller. Originally was designed to mimic Google Map API and being + * adapted to other providers. For detailed documentation on each method please refer Google Maps + * documentation. + */ +public interface AnyMap { + + void moveCamera(CameraUpdate cameraUpdate); + + void animateCamera(CameraUpdate cameraUpdate); + + void animateCamera(CameraUpdate cameraUpdate, CancelableCallback callback); + + void animateCamera(CameraUpdate cameraUpdate, int duration, CancelableCallback callback); + + CameraPosition getCameraPosition(); + + Projection getProjection(); + + Marker addMarker(MarkerOptions options); + + Circle addCircle(CircleOptions options); + + Polygon addPolygon(PolygonOptions options); + + Polyline addPolyline(PolylineOptions options); + + UiSettings getUiSettings(); + + void setOnMapClickListener(OnMapClickListener listener); + + void setOnMapLongClickListener(OnMapLongClickListener listener); + + void setOnCameraChangeListener(OnCameraChangeListener listener); + + void setOnMarkerClickListener(OnMarkerClickListener listener); + + void setInfoWindowAdapter(InfoWindowAdapter adapter); + + void setTrafficEnabled(boolean enabled); + + void setMyLocationEnabled(boolean enabled); + + void setMapType(Type type); + + void setPadding(int left, int top, int right, int bottom); + + void onUserLocationChanged(LatLng location, float accuracy); + + + enum Type { + + NORMAL, + SATELLITE + + } + + /** + * Features of {@link AnyMap} which might be supported or not supported + * by each particular implementation. + */ + enum Feature { + + /** + * Displaying layer with traffic jams on the map + */ + TRAFFIC_LAYER, + + /** + * Supporting several {@link com.car2go.maps.AnyMap.Type}. If this capability is not present, + * only one of types is implemented (which one - is not specified). + */ + MAP_TYPES, + + /** + * Supports being invisible at first and being revealed (or simply made visible) later on. + */ + REVEALABLE + + } + + interface OnMapClickListener { + + OnMapClickListener NULL = new OnMapClickListener() { + @Override + public void onMapClick(LatLng latLng) { + // Do nothing + } + }; + + void onMapClick(LatLng latLng); + + } + + interface OnMapLongClickListener { + + OnMapLongClickListener NULL = new OnMapLongClickListener() { + @Override + public void onMapLongClick(LatLng latLng) { + // Do nothing + } + }; + + void onMapLongClick(LatLng latLng); + + } + + interface OnCameraChangeListener { + + void onCameraChange(CameraPosition cameraPosition); + + } + + interface OnMarkerClickListener { + + OnMarkerClickListener NULL = new OnMarkerClickListener() { + @Override + public boolean onMarkerClick(Marker marker) { + // Do nothing + return false; + } + }; + + boolean onMarkerClick(Marker marker); + + } + + interface CancelableCallback { + + void onFinish(); + + void onCancel(); + + } + + interface InfoWindowAdapter { + + View getInfoWindow(Marker marker); + + View getInfoContents(Marker marker); + + } + +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/BitmapDescriptorFactory.java b/anymaps-base/src/main/java/com/car2go/maps/BitmapDescriptorFactory.java new file mode 100644 index 0000000..8820c55 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/BitmapDescriptorFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2017 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps; + +import android.graphics.Bitmap; +import android.support.annotation.DrawableRes; + +import com.car2go.maps.model.BitmapDescriptor; + +/** + * Factory for creating BitmapDescriptors. + */ +public interface BitmapDescriptorFactory { + /** + * @return new {@link BitmapDescriptor} from given {@link Bitmap} + */ + BitmapDescriptor fromBitmap(Bitmap bitmap); + + /** + * @return new {@link BitmapDescriptor} from given resource + */ + BitmapDescriptor fromResource(@DrawableRes int resourceId); +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/CameraUpdate.java b/anymaps-base/src/main/java/com/car2go/maps/CameraUpdate.java new file mode 100644 index 0000000..8d08f7a --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/CameraUpdate.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2017 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps; + +/** + * Mimics Google CameraUpdate + */ +public interface CameraUpdate { +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/CameraUpdateFactory.java b/anymaps-base/src/main/java/com/car2go/maps/CameraUpdateFactory.java new file mode 100644 index 0000000..6566067 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/CameraUpdateFactory.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2017 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps; + +import com.car2go.maps.model.LatLng; +import com.car2go.maps.model.LatLngBounds; + +/** + * Creates {@link CameraUpdate} objects which can be used to update map camera + */ +public interface CameraUpdateFactory { + + /** + * @return {@link CameraUpdate} which moves camera to given position with given zoom level. + */ + CameraUpdate newLatLngZoom(LatLng latLng, float zoomLevel); + + /** + * @return {@link CameraUpdate} which moves camera so it displays given bounds with given padding. + */ + CameraUpdate newLatLngBounds(LatLngBounds bounds, int padding); + + /** + * @return {@link CameraUpdate} which zooms camera to given zoom level. + */ + CameraUpdate zoomTo(float zoomLevel); +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/MapContainerView.java b/anymaps-base/src/main/java/com/car2go/maps/MapContainerView.java new file mode 100644 index 0000000..4aab0af --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/MapContainerView.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps; + +import android.content.Context; +import android.os.Bundle; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +/** + * View container for an {@link AnyMap}. + */ +public abstract class MapContainerView extends FrameLayout { + + protected MapContainerView(Context context) { + super(context); + } + + protected MapContainerView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + /** + * Gets the wrapped {@link AnyMap} asynchronously. + * + * @param callback the callback to use when the map has been got + */ + public abstract void getMapAsync(OnMapReadyCallback callback); + + /** + * Propagate the onCreate lifecycle call to the AnyMap. + * + * @param savedInstanceState the savedInstanceState + */ + public abstract void onCreate(Bundle savedInstanceState); + + /** + * Propagate the onResume lifecycle call to the AnyMap. + */ + public abstract void onResume(); + + /** + * Propagate the onPause lifecycle call to the AnyMap. + */ + public abstract void onPause(); + + /** + * Propagate the onDestroy lifecycle call to the AnyMap. + */ + public abstract void onDestroy(); + + /** + * Propagate the onLowMemory lifecycle call to the AnyMap. + */ + public abstract void onLowMemory(); + + /** + * Propagate the onSaveInstanceState lifecycle call to the AnyMap. + * + * @param outState the outState + */ + public abstract void onSaveInstanceState(Bundle outState); + +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/MapsConfiguration.java b/anymaps-base/src/main/java/com/car2go/maps/MapsConfiguration.java new file mode 100644 index 0000000..d3f0f25 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/MapsConfiguration.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2017 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps; + +import android.content.Context; + +import java.util.Set; + +/** + * A util class for initializing the map and retrieving its capabilities. + */ +public interface MapsConfiguration { + + /** + * Initializes the maps. + * + * @param context the context + */ + void initialize(Context context); + + /** + * Gets the supported features of the {@link AnyMap} implementation. If some features are not supported + * and you will try to call them - nothing will happen. + * + * @return capabilities of the {@link AnyMap} implementation. + */ + Set getSupportedFeatures(); +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/OnMapReadyCallback.java b/anymaps-base/src/main/java/com/car2go/maps/OnMapReadyCallback.java new file mode 100644 index 0000000..52f5e7d --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/OnMapReadyCallback.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2017 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps; + +/** + * Mimics Google OnMapReadyCallback + */ +public interface OnMapReadyCallback { + + void onMapReady(AnyMap anyMap); + +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/Projection.java b/anymaps-base/src/main/java/com/car2go/maps/Projection.java new file mode 100644 index 0000000..7807daf --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/Projection.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2017 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps; + +import com.car2go.maps.model.VisibleRegion; + +/** + * Mimics Google Projection + */ +public interface Projection { + + VisibleRegion getVisibleRegion(); + +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/UiSettings.java b/anymaps-base/src/main/java/com/car2go/maps/UiSettings.java new file mode 100644 index 0000000..7da5517 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/UiSettings.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2017 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps; + +/** + * Mimics Google UiSettings + */ +public interface UiSettings { + + void setAllGesturesEnabled(boolean enabled); + + void setMyLocationButtonEnabled(boolean enabled); + + void setMapToolbarEnabled(boolean enabled); + +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/BitmapDescriptor.java b/anymaps-base/src/main/java/com/car2go/maps/model/BitmapDescriptor.java new file mode 100644 index 0000000..503eaec --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/BitmapDescriptor.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.model; + +/** + * Mimics Google BitmapDescriptor + */ +public interface BitmapDescriptor { +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/CameraPosition.java b/anymaps-base/src/main/java/com/car2go/maps/model/CameraPosition.java new file mode 100644 index 0000000..2c2de40 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/CameraPosition.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.model; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Contains information about camera position on the map. + * Immutable. + */ +public class CameraPosition implements Parcelable { + + /** + * Center of the camera viewport + */ + public final LatLng target; + /** + * Zoom level of the camera + */ + public final float zoom; + + public CameraPosition(LatLng target, float zoom) { + this.target = target; + this.zoom = zoom; + } + + protected CameraPosition(Parcel in) { + this.target = in.readParcelable(LatLng.class.getClassLoader()); + this.zoom = in.readFloat(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CameraPosition)) { + return false; + } + + CameraPosition that = (CameraPosition) o; + + return Float.compare(that.zoom, zoom) == 0 && target.equals(that.target); + + } + + @Override + public int hashCode() { + int result = target.hashCode(); + result = 31 * result + (zoom != +0.0f ? Float.floatToIntBits(zoom) : 0); + return result; + } + + @Override + public String toString() { + return "CameraPosition{" + + "target=" + target + + ", zoom=" + zoom + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(this.target, flags); + dest.writeFloat(this.zoom); + } + + public static final Creator CREATOR = new Creator() { + public CameraPosition createFromParcel(Parcel source) { + return new CameraPosition(source); + } + + public CameraPosition[] newArray(int size) { + return new CameraPosition[size]; + } + }; + +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/Circle.java b/anymaps-base/src/main/java/com/car2go/maps/model/Circle.java new file mode 100644 index 0000000..3e7943d --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/Circle.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.model; + +/** + * Draws circle on the map + */ +public interface Circle extends DrawableComponent { +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/CircleOptions.java b/anymaps-base/src/main/java/com/car2go/maps/model/CircleOptions.java new file mode 100644 index 0000000..6c9f0fb --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/CircleOptions.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.model; + +import android.support.annotation.ColorInt; + +/** + * Accumulates parameters which are required to create {@link Circle} component. + * Mutable. + */ +public class CircleOptions { + + private LatLng center; + private double radius; + private int fillColor; + private int strokeColor; + private float strokeWidth; + + /** + * @param point center of the circle + * @return same {@link CircleOptions} + */ + public CircleOptions center(LatLng point) { + center = point; + return this; + } + + /** + * @param radius radius of the circle in meters + * @return same {@link CircleOptions} + */ + public CircleOptions radius(double radius) { + this.radius = radius; + return this; + } + + /** + * @param color color used to fill the circle + * @return same {@link CircleOptions} + */ + public CircleOptions fillColor(@ColorInt int color) { + fillColor = color; + return this; + } + + /** + * @param color color of the circle outline (stroke) + * @return same {@link CircleOptions} + */ + public CircleOptions strokeColor(@ColorInt int color) { + strokeColor = color; + return this; + } + + /** + * @param width width of the stroke in pixels + * @return same {@link CircleOptions} + */ + public CircleOptions strokeWidth(float width) { + strokeWidth = width; + return this; + } + + /** + * @see #center(LatLng) + */ + public LatLng getCenter() { + return center; + } + + /** + * @see #radius(double) + */ + public double getRadius() { + return radius; + } + + /** + * @see #fillColor(int) + */ + public int getFillColor() { + return fillColor; + } + + /** + * @see #strokeColor(int) + */ + public int getStrokeColor() { + return strokeColor; + } + + /** + * @see #strokeWidth(float) + */ + public float getStrokeWidth() { + return strokeWidth; + } +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/DrawableComponent.java b/anymaps-base/src/main/java/com/car2go/maps/model/DrawableComponent.java new file mode 100644 index 0000000..834b39c --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/DrawableComponent.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.model; + +/** + * Entity which can be drawn on map + */ +public interface DrawableComponent { + + /** + * Changes visibility of the component + * + * @param visible {@code true} to make component visible. + * {@code false} to make component invisible. + */ + void setVisible(boolean visible); + + /** + * Removes component from the map. If it's already removed, does nothing. + */ + void remove(); + +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/Geofence.java b/anymaps-base/src/main/java/com/car2go/maps/model/Geofence.java new file mode 100644 index 0000000..5389e19 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/Geofence.java @@ -0,0 +1,14 @@ +package com.car2go.maps.model; + +/** + * Defines handling of geofences + */ +public interface Geofence { + + /** + * Checks if a location is inside the geofence or not + * @param latLng location to change + * @return {@code true} if location is inside the geofence + */ + public boolean contains(LatLng latLng); +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/LatLng.java b/anymaps-base/src/main/java/com/car2go/maps/model/LatLng.java new file mode 100644 index 0000000..4b46022 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/LatLng.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.model; + +import android.location.Location; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Latitude/Longitude pair. + * Immutable. + */ +public class LatLng implements Parcelable { + + /** + * Latitude on the map + */ + public final double latitude; + /** + * Longitude on the map + */ + public final double longitude; + + public static LatLng fromLocation(Location location) { + return new LatLng(location.getLatitude(), location.getLongitude()); + } + + public LatLng(double latitude, double longitude) { + this.latitude = latitude; + this.longitude = longitude; + } + + protected LatLng(Parcel in) { + this.latitude = in.readDouble(); + this.longitude = in.readDouble(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof LatLng)) { + return false; + } + + LatLng latLng = (LatLng) o; + + return Double.compare(latLng.latitude, latitude) == 0 && Double.compare(latLng.longitude, longitude) == 0; + } + + @Override + public int hashCode() { + int result; + long temp; + temp = Double.doubleToLongBits(latitude); + result = (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(longitude); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public String toString() { + return "LatLng{" + + "latitude=" + latitude + + ", longitude=" + longitude + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeDouble(this.latitude); + dest.writeDouble(this.longitude); + } + + public static final Creator CREATOR = new Creator() { + public LatLng createFromParcel(Parcel source) { + return new LatLng(source); + } + + public LatLng[] newArray(int size) { + return new LatLng[size]; + } + }; + +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/LatLngBounds.java b/anymaps-base/src/main/java/com/car2go/maps/model/LatLngBounds.java new file mode 100644 index 0000000..6fedb48 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/LatLngBounds.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.model; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * (Pseudo)Rectangular region on the map. + * Immutable. + */ +public class LatLngBounds implements Parcelable { + + /** + * South-West point of the region. + */ + public final LatLng southwest; + /** + * North-East point of the region. + */ + public final LatLng northeast; + + public LatLngBounds(LatLng southwest, LatLng northeast) { + this.southwest = southwest; + this.northeast = northeast; + } + + protected LatLngBounds(Parcel in) { + this.southwest = in.readParcelable(LatLng.class.getClassLoader()); + this.northeast = in.readParcelable(LatLng.class.getClassLoader()); + } + + /** + * @return {@link com.car2go.maps.model.LatLngBounds.Builder} for {@link LatLngBounds} + */ + public static Builder builder() { + return new Builder(); + } + + /** + * @return center of the region + */ + public LatLng getCenter() { + // Implementation copied from original obfuscated version of LatLngBounds + + double var1 = (this.southwest.latitude + this.northeast.latitude) / 2.0D; + double var3 = this.northeast.longitude; + double var5 = this.southwest.longitude; + double var7; + if (var5 <= var3) { + var7 = (var3 + var5) / 2.0D; + } else { + var7 = (var3 + 360.0D + var5) / 2.0D; + } + + return new LatLng(var1, var7); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof LatLngBounds)) return false; + + LatLngBounds that = (LatLngBounds) o; + + return southwest.equals(that.southwest) && northeast.equals(that.northeast); + } + + @Override + public int hashCode() { + int result = southwest.hashCode(); + result = 31 * result + northeast.hashCode(); + return result; + } + + @Override + public String toString() { + return "LatLngBounds{" + + "southwest=" + southwest + + ", northeast=" + northeast + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(this.southwest, 0); + dest.writeParcelable(this.northeast, 0); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public LatLngBounds createFromParcel(Parcel source) { + return new LatLngBounds(source); + } + + public LatLngBounds[] newArray(int size) { + return new LatLngBounds[size]; + } + }; + + /** + * Builds new instances of {@link LatLngBounds} + */ + public static class Builder { + + private double southWestLattitude = 1.0D / 0.0; + private double northEastLattitude = -1.0D / 0.0; + + private double southWestLongitude = 0.0D / 0.0; + private double northEastLongitude = 0.0D / 0.0; + + /** + * Ensures that given point will be within output bounds. Output bounds guaranteed to be + * as small as possible and enclose all given points. + * + * @return same {@link com.car2go.maps.model.LatLngBounds.Builder} + */ + public Builder include(LatLng point) { + southWestLattitude = Math.min(southWestLattitude, point.latitude); + northEastLattitude = Math.max(northEastLattitude, point.latitude); + + if (Double.isNaN(southWestLongitude)) { + southWestLongitude = point.longitude; + northEastLongitude = point.longitude; + } else if (!withinBounds(point.longitude)) { + if (degreeDifference(southWestLongitude, point.longitude) < degreeDifference(point.longitude, northEastLongitude)) { + southWestLongitude = point.longitude; + } else { + northEastLongitude = point.longitude; + } + } + + return this; + } + + private double degreeDifference(double first, double second) { + return (first - second + 360.0D) % 360.0D; + } + + private boolean withinBounds(double longitude) { + return this.southWestLongitude <= this.northEastLongitude + ? this.southWestLongitude <= longitude && longitude <= this.northEastLongitude + : this.southWestLongitude <= longitude || longitude <= this.northEastLongitude; + } + + /** + * @return new instance of {@link LatLngBounds} + * @throws IllegalStateException if less than 2 unique points were specified + */ + public LatLngBounds build() { + if (Double.isNaN(southWestLongitude)) { + throw new IllegalStateException("No included points"); + } + + return new LatLngBounds( + new LatLng(southWestLattitude, southWestLongitude), + new LatLng(northEastLattitude, northEastLongitude) + ); + } + + } + +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/Marker.java b/anymaps-base/src/main/java/com/car2go/maps/model/Marker.java new file mode 100644 index 0000000..0f41971 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/Marker.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.model; + +import android.graphics.Bitmap; + +/** + * Draws marker (with icon) on the map + */ +public interface Marker extends DrawableComponent { + + /** + * Changes icon of the marker to given {@link Bitmap} + */ + void setIcon(BitmapDescriptor icon); + + /** + * @return current position of the marker + */ + LatLng getPosition(); + + /** + * Shows information window associated with this marker, if any. + */ + void showInfoWindow(); + + /** + * Sets the rotation of the marker. + * + * @param rotation the rotation value + */ + void setRotation(float rotation); + + /** + * Sets the Z index of the marker + * + * @param z z index of the marker + */ + void setZ(int z); + +} \ No newline at end of file diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/MarkerOptions.java b/anymaps-base/src/main/java/com/car2go/maps/model/MarkerOptions.java new file mode 100644 index 0000000..d33023e --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/MarkerOptions.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.model; + +/** + * Accumulates parameters which are required to create {@link Marker} component. + * Mutable. + */ +public class MarkerOptions { + + private float alpha = 1f; + private LatLng position; + private boolean visible = true; + private float anchorU = 0f; + private float anchorV = 0f; + private BitmapDescriptor icon; + private int z = 0; + + /** + * @param alpha alpha-level of the marker. In range [0..1]. Default value is 1. + * @return same {@link MarkerOptions} + */ + public MarkerOptions alpha(float alpha) { + this.alpha = alpha; + return this; + } + + /** + * @param position position of the marker's anchor on the map + * @return same {@link MarkerOptions} + */ + public MarkerOptions position(LatLng position) { + this.position = position; + return this; + } + + /** + * @param visible {@code true} to make marker visible by default. {@code false} to make marker + * invisible by default. Default value is {@code true}. + * @return same {@link MarkerOptions} + */ + public MarkerOptions visible(boolean visible) { + this.visible = visible; + return this; + } + + /** + * Specifies anchor of the marker (which part of marker's icon is considered position of the + * marker on the map). (0, 0) denotes top left corner. (1, 1) denotes bottom right corner. + * + * @param u U coordinate of the anchor relatively to the icon. Default value is 0. + * @param v V coordinate of the anchor relatively to the icon. Default value is 0. + * @return same {@link MarkerOptions} + */ + public MarkerOptions anchor(float u, float v) { + anchorU = u; + anchorV = v; + return this; + } + + /** + * @param icon icon of the marker + * @return same {@link MarkerOptions} + */ + public MarkerOptions icon(BitmapDescriptor icon) { + this.icon = icon; + return this; + } + + public MarkerOptions z(int z) { + this.z = z; + return this; + } + + /** + * @see #alpha(float) + */ + public float getAlpha() { + return alpha; + } + + /** + * @see #position(LatLng) + */ + public LatLng getPosition() { + return position; + } + + /** + * @see #visible(boolean) + */ + public boolean isVisible() { + return visible; + } + + /** + * @see #anchor(float, float) + */ + public float getAnchorU() { + return anchorU; + } + + /** + * @see #anchor(float, float) + */ + public float getAnchorV() { + return anchorV; + } + + /** + * @see #icon(BitmapDescriptor) + */ + public BitmapDescriptor getIcon() { + return icon; + } + + /** + * @see #z(int) + */ + public int getZ() { + return z; + } + +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/Polygon.java b/anymaps-base/src/main/java/com/car2go/maps/model/Polygon.java new file mode 100644 index 0000000..11e4924 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/Polygon.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.model; + +import java.util.List; + +/** + * Draws polygon on the map. Might contain holes within the polygon. + */ +public interface Polygon extends DrawableComponent { + + /** + * @param holes holes within the polygon area. If holes are outside of the polygon, behavior + * is undefined. + */ + void setHoles(List> holes); + + /** + * @return a snapshot of the vertices of this polygon at this time. + * The list returned is a copy of the list of vertices and so changes to the polygon's vertices + * will not be reflected by this list, nor will changes to this list be reflected by the polygon. + */ + List getPoints(); + +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/PolygonOptions.java b/anymaps-base/src/main/java/com/car2go/maps/model/PolygonOptions.java new file mode 100644 index 0000000..35319da --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/PolygonOptions.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.model; + +import android.support.annotation.ColorInt; + +import java.util.ArrayList; +import java.util.List; + +/** + * Accumulates parameters which are required to create {@link Polygon} component. + * Mutable. + */ +public class PolygonOptions { + + private final List points = new ArrayList<>(); + private int fillColor; + private float strokeWidth; + private int strokeColor; + private boolean outsider = false; + + /** + * @param color color used to fill the polygon + * @return same {@link PolygonOptions} + */ + public PolygonOptions fillColor(@ColorInt int color) { + fillColor = color; + return this; + } + + /** + * @param width width of the polygon outline in pixels. + * @return same {@link PolygonOptions} + */ + public PolygonOptions strokeWidth(float width) { + strokeWidth = width; + return this; + } + + /** + * @param color color of the polygon outline + * @return same {@link PolygonOptions} + */ + public PolygonOptions strokeColor(@ColorInt int color) { + strokeColor = color; + return this; + } + + /** + * @param outsider {@code true} to invert filling of this polygon. That is, filling everything + * with color except for the holes. {@code false} for normal drawing routine. + * @return same {@link PolygonOptions} + */ + public PolygonOptions outsider(boolean outsider) { + this.outsider = outsider; + return this; + } + + /** + * Adds given point to the polygon + * + * @return same {@link PolygonOptions} + */ + public PolygonOptions add(LatLng point) { + points.add(point); + return this; + } + + /** + * Adds all points from list to the polygon + * + * @return same {@link PolygonOptions} + */ + public PolygonOptions addAll(List points) { + this.points.addAll(points); + return this; + } + + /** + * @see #fillColor(int) + */ + public int getFillColor() { + return fillColor; + } + + /** + * @see #strokeWidth(float) + */ + public float getStrokeWidth() { + return strokeWidth; + } + + /** + * @see #strokeColor(int) + */ + public int getStrokeColor() { + return strokeColor; + } + + /** + * @see #outsider(boolean) + */ + public boolean isOutsider() { + return outsider; + } + + /** + * @see #add(LatLng) + * @see #addAll(List) + */ + public List getPoints() { + return points; + } + +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/Polyline.java b/anymaps-base/src/main/java/com/car2go/maps/model/Polyline.java new file mode 100644 index 0000000..bda4404 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/Polyline.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.model; + +/** + * Draws polyline on the map + */ +public interface Polyline extends DrawableComponent { +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/PolylineOptions.java b/anymaps-base/src/main/java/com/car2go/maps/model/PolylineOptions.java new file mode 100644 index 0000000..d8641a0 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/PolylineOptions.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.model; + +import android.support.annotation.ColorInt; + +import java.util.ArrayList; +import java.util.List; + +/** + * Accumulates parameters which are required to create {@link Polyline} component. + * Mutable. + */ +public class PolylineOptions { + + private int color; + private float width; + private final List points = new ArrayList<>(); + + /** + * @param color color of the line + * @return same {@link PolylineOptions} + */ + public PolylineOptions color(@ColorInt int color) { + this.color = color; + return this; + } + + /** + * @param width width of the line in pixels + * @return same {@link PolylineOptions} + */ + public PolylineOptions width(float width) { + this.width = width; + return this; + } + + /** + * Adds point to polyline + * + * @return same {@link PolylineOptions} + */ + public PolylineOptions add(LatLng point) { + points.add(point); + return this; + } + + /** + * Adds all points from list to polyline + * + * @return same {@link PolylineOptions} + */ + public PolylineOptions addAll(List points) { + this.points.addAll(points); + return this; + } + + /** + * @see #color(int) + */ + public int getColor() { + return color; + } + + /** + * @see #width(float) + */ + public float getWidth() { + return width; + } + + /** + * @see #add(LatLng) + * @see #addAll(List) + */ + public List getPoints() { + return points; + } +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/RectGeofence.java b/anymaps-base/src/main/java/com/car2go/maps/model/RectGeofence.java new file mode 100644 index 0000000..4174de5 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/RectGeofence.java @@ -0,0 +1,34 @@ +package com.car2go.maps.model; + +/** + * A rectangular geofence composed of two locations. + * + * The locations should be the North-West corner and the South-East corner of the rect. + */ +public class RectGeofence implements Geofence { + private final LatLng northWest; + private final LatLng southEast; + + public RectGeofence(LatLng northWest, LatLng southEast) { + this.northWest = northWest; + this.southEast = southEast; + + if ((northWest.latitude <= southEast.latitude) || (northWest.longitude >= southEast.longitude)) { + throw new IllegalArgumentException("North West point should be in the Top Left corner of the rect"); + } + } + + @Override + public boolean contains(LatLng latLng) { + double longitude = latLng.longitude; + double latitude = latLng.latitude; + + double leftBorder = northWest.longitude; + double rightBorder = southEast.longitude; + + double bottomBorder = southEast.latitude; + double topBorder = northWest.latitude; + + return latitude >= bottomBorder && latitude <= topBorder && longitude >= leftBorder && longitude <= rightBorder; + } +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/model/VisibleRegion.java b/anymaps-base/src/main/java/com/car2go/maps/model/VisibleRegion.java new file mode 100644 index 0000000..d4fb3f1 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/model/VisibleRegion.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.model; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Visible region on the map. + * Immutable. + */ +public class VisibleRegion implements Parcelable { + + /** + * Currently visible bounds. + */ + public final LatLngBounds latLngBounds; + + public VisibleRegion(LatLngBounds latLngBounds) { + this.latLngBounds = latLngBounds; + } + + protected VisibleRegion(Parcel in) { + this.latLngBounds = in.readParcelable(LatLngBounds.class.getClassLoader()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof VisibleRegion)) { + return false; + } + + VisibleRegion that = (VisibleRegion) o; + + return latLngBounds.equals(that.latLngBounds); + } + + @Override + public int hashCode() { + return latLngBounds.hashCode(); + } + + @Override + public String toString() { + return "VisibleRegion{" + + "latLngBounds=" + latLngBounds + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(this.latLngBounds, flags); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public VisibleRegion createFromParcel(Parcel source) { + return new VisibleRegion(source); + } + + public VisibleRegion[] newArray(int size) { + return new VisibleRegion[size]; + } + }; + +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/util/MathUtil.java b/anymaps-base/src/main/java/com/car2go/maps/util/MathUtil.java new file mode 100644 index 0000000..abd772a --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/util/MathUtil.java @@ -0,0 +1,124 @@ +/* + * Copyright 2013 Google Inc. + * + * 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. + */ + +package com.car2go.maps.util; + +/** + * Utility functions that are used my both PolyUtil and SphericalUtil. + */ +class MathUtil { + + /** + * The earth's radius, in meters. + * Mean radius as defined by IUGG. + */ + static final double EARTH_RADIUS = 6371009.0D; + + MathUtil() { + } + + /** + * Restrict x to the range [low, high]. + */ + static double clamp(double x, double low, double high) { + return x < low ? low : (x > high ? high : x); + } + + /** + * Wraps the given value into the inclusive-exclusive interval between min and max. + * + * @param n The value to wrap. + * @param min The minimum. + * @param max The maximum. + */ + static double wrap(double n, double min, double max) { + return n >= min && n < max ? n : mod(n - min, max - min) + min; + } + + /** + * Returns the non-negative remainder of x / m. + * + * @param x The operand. + * @param m The modulus. + */ + static double mod(double x, double m) { + return (x % m + m) % m; + } + + /** + * Returns mercator Y corresponding to latitude. + * See http://en.wikipedia.org/wiki/Mercator_projection . + */ + static double mercator(double lat) { + return Math.log(Math.tan(lat * 0.5D + 0.7853981633974483D)); + } + + /** + * Returns latitude from mercator Y. + */ + static double inverseMercator(double y) { + return 2.0D * Math.atan(Math.exp(y)) - 1.5707963267948966D; + } + + /** + * Returns haversine(angle-in-radians). + * hav(x) == (1 - cos(x)) / 2 == sin(x / 2)^2. + */ + static double hav(double x) { + double sinHalf = Math.sin(x * 0.5D); + return sinHalf * sinHalf; + } + + /** + * Computes inverse haversine. Has good numerical stability around 0. + * arcHav(x) == acos(1 - 2 * x) == 2 * asin(sqrt(x)). + * The argument must be in [0, 1], and the result is positive. + */ + static double arcHav(double x) { + return 2.0D * Math.asin(Math.sqrt(x)); + } + + /** + * Given h==hav(x), returns sin(abs(x)). + */ + static double sinFromHav(double h) { + return 2.0D * Math.sqrt(h * (1.0D - h)); + } + + /** + * Returns hav(asin(x)). + */ + static double havFromSin(double x) { + double x2 = x * x; + return x2 / (1.0D + Math.sqrt(1.0D - x2)) * 0.5D; + } + + /** + * Returns sin(arcHav(x) + arcHav(y)). + */ + static double sinSumFromHav(double x, double y) { + double a = Math.sqrt(x * (1.0D - x)); + double b = Math.sqrt(y * (1.0D - y)); + return 2.0D * (a + b - 2.0D * (a * y + b * x)); + } + + /** + * Returns hav() of distance from (lat1, lng1) to (lat2, lng2) on the unit sphere. + */ + static double havDistance(double lat1, double lat2, double dLng) { + return hav(lat1 - lat2) + hav(dLng) * Math.cos(lat1) * Math.cos(lat2); + } +} \ No newline at end of file diff --git a/anymaps-base/src/main/java/com/car2go/maps/util/PolyUtil.java b/anymaps-base/src/main/java/com/car2go/maps/util/PolyUtil.java new file mode 100644 index 0000000..14c5cd9 --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/util/PolyUtil.java @@ -0,0 +1,146 @@ +/* + * Copyright 2013 Google Inc. + * + * 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. + */ + +package com.car2go.maps.util; + + +import com.car2go.maps.model.LatLng; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class PolyUtil { + + /** + * Decodes an encoded path string into a sequence of LatLngs. + */ + public static List decode(String encodedPath) { + int len = encodedPath.length(); + ArrayList path = new ArrayList<>(); + int index = 0; + int lat = 0; + int lng = 0; + + while (index < len) { + int result = 1; + int shift = 0; + + int b; + do { + b = encodedPath.charAt(index++) - 63 - 1; + result += b << shift; + shift += 5; + } while (b >= 31); + + lat += (result & 1) != 0 ? ~(result >> 1) : result >> 1; + result = 1; + shift = 0; + + do { + b = encodedPath.charAt(index++) - 63 - 1; + result += b << shift; + shift += 5; + } while (b >= 31); + + lng += (result & 1) != 0 ? ~(result >> 1) : result >> 1; + path.add(new LatLng((double) lat * 1.0E-5D, (double) lng * 1.0E-5D)); + } + + return path; + } + + /** + * Returns tan(latitude-at-lng3) on the great circle (lat1, lng1) to (lat2, lng2). lng1==0. + * See http://williams.best.vwh.net/avform.htm . + */ + private static double tanLatGC(double lat1, double lat2, double lng2, double lng3) { + return (Math.tan(lat1) * Math.sin(lng2 - lng3) + Math.tan(lat2) * Math.sin(lng3)) / Math.sin(lng2); + } + + /** + * Returns mercator(latitude-at-lng3) on the Rhumb line (lat1, lng1) to (lat2, lng2). lng1==0. + */ + private static double mercatorLatRhumb(double lat1, double lat2, double lng2, double lng3) { + return (MathUtil.mercator(lat1) * (lng2 - lng3) + MathUtil.mercator(lat2) * lng3) / lng2; + } + + /** + * Computes whether the vertical segment (lat3, lng3) to South Pole intersects the segment + * (lat1, lng1) to (lat2, lng2). + * Longitudes are offset by -lng1; the implicit lng1 becomes 0. + */ + private static boolean intersects(double lat1, double lat2, double lng2, double lat3, double lng3, boolean geodesic) { + if ((lng3 < 0.0D || lng3 < lng2) && (lng3 >= 0.0D || lng3 >= lng2)) { + if (lat3 <= -1.5707963267948966D) { + return false; + } else if (lat1 > -1.5707963267948966D && lat2 > -1.5707963267948966D && lat1 < 1.5707963267948966D && lat2 < 1.5707963267948966D) { + if (lng2 <= -3.141592653589793D) { + return false; + } else { + double linearLat = (lat1 * (lng2 - lng3) + lat2 * lng3) / lng2; + return lat1 >= 0.0D && lat2 >= 0.0D && lat3 < linearLat ? false : (lat1 <= 0.0D && lat2 <= 0.0D && lat3 >= linearLat ? true : (lat3 >= 1.5707963267948966D ? true : (geodesic ? Math.tan(lat3) >= tanLatGC(lat1, lat2, lng2, lng3) : MathUtil.mercator(lat3) >= mercatorLatRhumb(lat1, lat2, lng2, lng3)))); + } + } else { + return false; + } + } else { + return false; + } + } + + /** + * Computes whether the given point lies inside the specified polygon. + * The polygon is always cosidered closed, regardless of whether the last point equals + * the first or not. + * Inside is defined as not containing the South Pole -- the South Pole is always outside. + * The polygon is formed of great circle segments if geodesic is true, and of rhumb + * (loxodromic) segments otherwise. + */ + public static boolean containsLocation(LatLng point, List polygon, boolean geodesic) { + int size = polygon.size(); + if (size == 0) { + return false; + } else { + double lat3 = Math.toRadians(point.latitude); + double lng3 = Math.toRadians(point.longitude); + LatLng prev = polygon.get(size - 1); + double lat1 = Math.toRadians(prev.latitude); + double lng1 = Math.toRadians(prev.longitude); + int nIntersect = 0; + + double lng2; + for (Iterator i$ = polygon.iterator(); i$.hasNext(); lng1 = lng2) { + LatLng point2 = (LatLng) i$.next(); + double dLng3 = MathUtil.wrap(lng3 - lng1, -3.141592653589793D, 3.141592653589793D); + if (lat3 == lat1 && dLng3 == 0.0D) { + return true; + } + + double lat2 = Math.toRadians(point2.latitude); + lng2 = Math.toRadians(point2.longitude); + if (intersects(lat1, lat2, MathUtil.wrap(lng2 - lng1, -3.141592653589793D, 3.141592653589793D), lat3, dLng3, geodesic)) { + ++nIntersect; + } + + lat1 = lat2; + } + + return (nIntersect & 1) != 0; + } + + } +} diff --git a/anymaps-base/src/main/java/com/car2go/maps/util/SphericalUtil.java b/anymaps-base/src/main/java/com/car2go/maps/util/SphericalUtil.java new file mode 100644 index 0000000..b6ff79f --- /dev/null +++ b/anymaps-base/src/main/java/com/car2go/maps/util/SphericalUtil.java @@ -0,0 +1,250 @@ +/* + * Copyright 2013 Google Inc. + * + * 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. + */ + +package com.car2go.maps.util; + + +import com.car2go.maps.model.LatLng; + +import java.util.Iterator; +import java.util.List; + +public class SphericalUtil { + + private SphericalUtil() { + } + + /** + * Returns the heading from one LatLng to another LatLng. Headings are + * expressed in degrees clockwise from North within the range [-180,180). + * + * @return The heading in degrees clockwise from north. + */ + public static double computeHeading(LatLng from, LatLng to) { + double fromLat = Math.toRadians(from.latitude); + double fromLng = Math.toRadians(from.longitude); + double toLat = Math.toRadians(to.latitude); + double toLng = Math.toRadians(to.longitude); + double dLng = toLng - fromLng; + double heading = Math.atan2(Math.sin(dLng) * Math.cos(toLat), Math.cos(fromLat) * Math.sin(toLat) - Math.sin(fromLat) * Math.cos(toLat) * Math.cos(dLng)); + return MathUtil.wrap(Math.toDegrees(heading), -180.0D, 180.0D); + } + + /** + * Returns the LatLng resulting from moving a distance from an origin + * in the specified heading (expressed in degrees clockwise from north). + * + * @param from The LatLng from which to start. + * @param distance The distance to travel. + * @param heading The heading in degrees clockwise from north. + */ + public static LatLng computeOffset(LatLng from, double distance, double heading) { + distance /= 6371009.0D; + heading = Math.toRadians(heading); + double fromLat = Math.toRadians(from.latitude); + double fromLng = Math.toRadians(from.longitude); + double cosDistance = Math.cos(distance); + double sinDistance = Math.sin(distance); + double sinFromLat = Math.sin(fromLat); + double cosFromLat = Math.cos(fromLat); + double sinLat = cosDistance * sinFromLat + sinDistance * cosFromLat * Math.cos(heading); + double dLng = Math.atan2(sinDistance * cosFromLat * Math.sin(heading), cosDistance - sinFromLat * sinLat); + return new LatLng(Math.toDegrees(Math.asin(sinLat)), Math.toDegrees(fromLng + dLng)); + } + + /** + * Returns the location of origin when provided with a LatLng destination, + * meters travelled and original heading. Headings are expressed in degrees + * clockwise from North. This function returns null when no solution is + * available. + * + * @param to The destination LatLng. + * @param distance The distance travelled, in meters. + * @param heading The heading in degrees clockwise from north. + */ + public static LatLng computeOffsetOrigin(LatLng to, double distance, double heading) { + heading = Math.toRadians(heading); + distance /= 6371009.0D; + double n1 = Math.cos(distance); + double n2 = Math.sin(distance) * Math.cos(heading); + double n3 = Math.sin(distance) * Math.sin(heading); + double n4 = Math.sin(Math.toRadians(to.latitude)); + double n12 = n1 * n1; + double discriminant = n2 * n2 * n12 + n12 * n12 - n12 * n4 * n4; + if (discriminant < 0.0D) { + return null; + } else { + double b = n2 * n4 + Math.sqrt(discriminant); + b /= n1 * n1 + n2 * n2; + double a = (n4 - n2 * b) / n1; + double fromLatRadians = Math.atan2(a, b); + if (fromLatRadians < -1.5707963267948966D || fromLatRadians > 1.5707963267948966D) { + b = n2 * n4 - Math.sqrt(discriminant); + b /= n1 * n1 + n2 * n2; + fromLatRadians = Math.atan2(a, b); + } + + if (fromLatRadians >= -1.5707963267948966D && fromLatRadians <= 1.5707963267948966D) { + double fromLngRadians = Math.toRadians(to.longitude) - Math.atan2(n3, n1 * Math.cos(fromLatRadians) - n2 * Math.sin(fromLatRadians)); + return new LatLng(Math.toDegrees(fromLatRadians), Math.toDegrees(fromLngRadians)); + } else { + return null; + } + } + } + + /** + * Returns the LatLng which lies the given fraction of the way between the + * origin LatLng and the destination LatLng. + * + * @param from The LatLng from which to start. + * @param to The LatLng toward which to travel. + * @param fraction A fraction of the distance to travel. + * @return The interpolated LatLng. + */ + public static LatLng interpolate(LatLng from, LatLng to, double fraction) { + double fromLat = Math.toRadians(from.latitude); + double fromLng = Math.toRadians(from.longitude); + double toLat = Math.toRadians(to.latitude); + double toLng = Math.toRadians(to.longitude); + double cosFromLat = Math.cos(fromLat); + double cosToLat = Math.cos(toLat); + double angle = computeAngleBetween(from, to); + double sinAngle = Math.sin(angle); + if (sinAngle < 1.0E-6D) { + return from; + } else { + double a = Math.sin((1.0D - fraction) * angle) / sinAngle; + double b = Math.sin(fraction * angle) / sinAngle; + double x = a * cosFromLat * Math.cos(fromLng) + b * cosToLat * Math.cos(toLng); + double y = a * cosFromLat * Math.sin(fromLng) + b * cosToLat * Math.sin(toLng); + double z = a * Math.sin(fromLat) + b * Math.sin(toLat); + double lat = Math.atan2(z, Math.sqrt(x * x + y * y)); + double lng = Math.atan2(y, x); + return new LatLng(Math.toDegrees(lat), Math.toDegrees(lng)); + } + } + + /** + * Returns distance on the unit sphere; the arguments are in radians. + */ + private static double distanceRadians(double lat1, double lng1, double lat2, double lng2) { + return MathUtil.arcHav(MathUtil.havDistance(lat1, lat2, lng1 - lng2)); + } + + /** + * Returns the angle between two LatLngs, in radians. This is the same as the distance + * on the unit sphere. + */ + static double computeAngleBetween(LatLng from, LatLng to) { + return distanceRadians(Math.toRadians(from.latitude), Math.toRadians(from.longitude), Math.toRadians(to.latitude), Math.toRadians(to.longitude)); + } + + /** + * Returns the distance between two LatLngs, in meters. + */ + public static double computeDistanceBetween(LatLng from, LatLng to) { + return computeAngleBetween(from, to) * 6371009.0D; + } + + /** + * Returns the length of the given path, in meters, on Earth. + */ + public static double computeLength(List path) { + if (path.size() < 2) { + return 0.0D; + } else { + double length = 0.0D; + LatLng prev = (LatLng) path.get(0); + double prevLat = Math.toRadians(prev.latitude); + double prevLng = Math.toRadians(prev.longitude); + + double lng; + for (Iterator i$ = path.iterator(); i$.hasNext(); prevLng = lng) { + LatLng point = (LatLng) i$.next(); + double lat = Math.toRadians(point.latitude); + lng = Math.toRadians(point.longitude); + length += distanceRadians(prevLat, prevLng, lat, lng); + prevLat = lat; + } + + return length * 6371009.0D; + } + } + + /** + * Returns the area of a closed path on Earth. + * + * @param path A closed path. + * @return The path's area in square meters. + */ + public static double computeArea(List path) { + return Math.abs(computeSignedArea(path)); + } + + /** + * Returns the signed area of a closed path on Earth. The sign of the area may be used to + * determine the orientation of the path. + * "inside" is the surface that does not contain the South Pole. + * + * @param path A closed path. + * @return The loop's area in square meters. + */ + public static double computeSignedArea(List path) { + return computeSignedArea(path, 6371009.0D); + } + + /** + * Returns the signed area of a closed path on a sphere of given radius. + * The computed area uses the same units as the radius squared. + * Used by SphericalUtilTest. + */ + static double computeSignedArea(List path, double radius) { + int size = path.size(); + if (size < 3) { + return 0.0D; + } else { + double total = 0.0D; + LatLng prev = (LatLng) path.get(size - 1); + double prevTanLat = Math.tan((1.5707963267948966D - Math.toRadians(prev.latitude)) / 2.0D); + double prevLng = Math.toRadians(prev.longitude); + + double lng; + for (Iterator i$ = path.iterator(); i$.hasNext(); prevLng = lng) { + LatLng point = (LatLng) i$.next(); + double tanLat = Math.tan((1.5707963267948966D - Math.toRadians(point.latitude)) / 2.0D); + lng = Math.toRadians(point.longitude); + total += polarTriangleArea(tanLat, lng, prevTanLat, prevLng); + prevTanLat = tanLat; + } + + return total * radius * radius; + } + } + + /** + * Returns the signed area of a triangle which has North Pole as a vertex. + * Formula derived from "Area of a spherical triangle given two edges and the included angle" + * as per "Spherical Trigonometry" by Todhunter, page 71, section 103, point 2. + * See http://books.google.com/books?id=3uBHAAAAIAAJ&pg=PA71 + * The arguments named "tan" are tan((pi/2 - latitude)/2). + */ + private static double polarTriangleArea(double tan1, double lng1, double tan2, double lng2) { + double deltaLng = lng1 - lng2; + double t = tan1 * tan2; + return 2.0D * Math.atan2(t * Math.sin(deltaLng), 1.0D + t * Math.cos(deltaLng)); + } +} \ No newline at end of file diff --git a/anymaps-base/src/main/res/values/attrs.xml b/anymaps-base/src/main/res/values/attrs.xml new file mode 100644 index 0000000..64ad4d5 --- /dev/null +++ b/anymaps-base/src/main/res/values/attrs.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/anymaps-base/src/test/java/com/car2go/maps/model/RectGeofenceTest.java b/anymaps-base/src/test/java/com/car2go/maps/model/RectGeofenceTest.java new file mode 100644 index 0000000..a147d93 --- /dev/null +++ b/anymaps-base/src/test/java/com/car2go/maps/model/RectGeofenceTest.java @@ -0,0 +1,46 @@ +package com.car2go.maps.model; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests for the functionality of the RectGeofence + */ +@RunWith(JUnit4.class) +public class RectGeofenceTest { + + private RectGeofence createFence() { + return new RectGeofence(new LatLng(1, -1), new LatLng(-1, 1)); + } + + @Test + public void testInside() throws Exception { + assertTrue(createFence().contains(new LatLng(0, 0))); + assertTrue(createFence().contains(new LatLng(0.999, 0.999))); + assertTrue(createFence().contains(new LatLng(-0.999, 0.999))); + assertTrue(createFence().contains(new LatLng(-0.999, -0.999))); + assertTrue(createFence().contains(new LatLng(0.999, -0.999))); + } + + @Test + public void testOutside() throws Exception { + assertFalse(createFence().contains(new LatLng(1.001, 0))); + assertFalse(createFence().contains(new LatLng(-1.001, 0))); + assertFalse(createFence().contains(new LatLng(0, 1.001))); + assertFalse(createFence().contains(new LatLng(0, -1.001))); + } + + @Test(expected = IllegalArgumentException.class) + public void testWrongRectEastWest() throws Exception { + new RectGeofence(new LatLng(1, 1), new LatLng(-1, -1)); + } + + @Test(expected = IllegalArgumentException.class) + public void testWrongRectNorthSouth() throws Exception { + new RectGeofence(new LatLng(-1, -1), new LatLng(1, 1)); + } +} diff --git a/anymaps-osm/.gitignore b/anymaps-osm/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/anymaps-osm/.gitignore @@ -0,0 +1 @@ +/build diff --git a/anymaps-osm/build.gradle b/anymaps-osm/build.gradle new file mode 100644 index 0000000..21fe820 --- /dev/null +++ b/anymaps-osm/build.gradle @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ +ext { + name = 'AnyMaps - Open Street Maps' + artifactId = 'anymaps.osm' + description = 'Open Street Maps version of AnyMaps' +} + +apply plugin: 'com.android.library' +apply plugin: 'maven' + +android { + compileSdkVersion 28 + buildToolsVersion "28.0.3" + + defaultConfig { + minSdkVersion 15 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + } + } +} + +dependencies { + implementation project(':anymaps-base') + + implementation 'org.slf4j:slf4j-simple:1.6.1' + implementation 'org.osmdroid:osmdroid-android:6.0.3' + + testImplementation 'junit:junit:4.12' + testImplementation 'org.mockito:mockito-core:1.9.5' +} diff --git a/anymaps-osm/src/main/AndroidManifest.xml b/anymaps-osm/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2ecf75f --- /dev/null +++ b/anymaps-osm/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/BitmapDescriptorFactory.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/BitmapDescriptorFactory.java new file mode 100644 index 0000000..814c99e --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/BitmapDescriptorFactory.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import com.car2go.maps.model.BitmapDescriptor; + +/** + * Creates instances of {@link BitmapDescriptor} + */ +public class BitmapDescriptorFactory implements com.car2go.maps.BitmapDescriptorFactory { + + @SuppressLint("StaticFieldLeak") + private static Context context; + + @SuppressLint("StaticFieldLeak") + private static final BitmapDescriptorFactory instance = new BitmapDescriptorFactory(); + + private BitmapDescriptorFactory() { + } + + public static BitmapDescriptorFactory getInstance() { + return instance; + } + + static void initialize(Context context) { + BitmapDescriptorFactory.context = context.getApplicationContext(); + } + + @Override + public BitmapDescriptor fromBitmap(Bitmap bitmap) { + ensureInitialized(); + + return new OsmBitmapDescriptor(bitmap); + } + + @Override + public BitmapDescriptor fromResource(int resourceId) { + ensureInitialized(); + + return new OsmBitmapDescriptor( + BitmapFactory.decodeResource(context.getResources(), resourceId) + ); + } + + private static void ensureInitialized() { + if (context == null) { + throw new IllegalStateException("Not initialized. Did you forgot to initialize MapsConfiguration?"); + } + } + +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/CameraUpdateFactory.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/CameraUpdateFactory.java new file mode 100644 index 0000000..e2f408d --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/CameraUpdateFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm; + +import com.car2go.maps.CameraUpdate; +import com.car2go.maps.model.LatLng; +import com.car2go.maps.model.LatLngBounds; + +/** + * Creates {@link CameraUpdate} objects which can be used to update map camera + */ +public class CameraUpdateFactory implements com.car2go.maps.CameraUpdateFactory { + + private static final CameraUpdateFactory instance = new CameraUpdateFactory(); + + private CameraUpdateFactory() { + } + + public static CameraUpdateFactory getInstance() { + return instance; + } + + @Override + public CameraUpdate newLatLngZoom(LatLng center, float zoomLevel) { + return new OsmCameraUpdate.Builder() + .center(center) + .zoom(zoomLevel) + .build(); + } + + @Override + public CameraUpdate newLatLngBounds(LatLngBounds bounds, int padding) { + return new OsmCameraUpdate.Builder() + .bounds(bounds) + .padding(padding) + .build(); + } + + @Override + public CameraUpdate zoomTo(float zoomLevel) { + return new OsmCameraUpdate.Builder() + .zoom(zoomLevel) + .build(); + } + +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/CameraUpdateHandler.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/CameraUpdateHandler.java new file mode 100644 index 0000000..005f6b3 --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/CameraUpdateHandler.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm; + +import com.car2go.maps.AnyMap; +import com.car2go.maps.CameraUpdate; +import com.car2go.maps.model.LatLng; + +import org.osmdroid.api.IMapController; +import org.osmdroid.views.MapView; + +import static com.car2go.maps.osm.util.OsmUtils.toGeoPoint; + +/** + * Handles incoming {@link CameraUpdate} events + */ +class CameraUpdateHandler { + + private final org.osmdroid.views.MapView map; + + public CameraUpdateHandler(MapView map) { + this.map = map; + } + + /** + * @see AnyMap#moveCamera(CameraUpdate) + */ + public void moveCamera(CameraUpdate cameraUpdate) { + final OsmCameraUpdate osmCameraUpdate = (OsmCameraUpdate) cameraUpdate; + + final IMapController controller = map.getController(); + + if (osmCameraUpdate.bounds != null) { + final LatLng center = osmCameraUpdate.bounds.getCenter(); + + controller.setZoom(map.getMaxZoomLevel()); + + controller.zoomToSpan( + (int) ((osmCameraUpdate.bounds.northeast.latitude - osmCameraUpdate.bounds.southwest.latitude) * 1e6), + (int) ((osmCameraUpdate.bounds.northeast.longitude - osmCameraUpdate.bounds.southwest.longitude) * 1e6) + ); + + controller.setCenter( + toGeoPoint(center) + ); + + return; + } + + if (osmCameraUpdate.zoom != null) { + controller.setZoom(osmCameraUpdate.zoom.intValue()); + } + + if (osmCameraUpdate.center != null) { + controller.setCenter( + toGeoPoint(osmCameraUpdate.center) + ); + } + } + + /** + * @see AnyMap#animateCamera(CameraUpdate) + */ + public void animateCamera(CameraUpdate cameraUpdate) { + moveCamera(cameraUpdate); + } + + /** + * @see AnyMap#animateCamera(CameraUpdate, AnyMap.CancelableCallback) + */ + public void animateCamera(CameraUpdate cameraUpdate, AnyMap.CancelableCallback callback) { + moveCamera(cameraUpdate); + + callback.onFinish(); + } + + /** + * @see AnyMap#animateCamera(CameraUpdate, int, AnyMap.CancelableCallback) + */ + public void animateCamera(CameraUpdate cameraUpdate, int duration, AnyMap.CancelableCallback callback) { + moveCamera(cameraUpdate); + + callback.onFinish(); + } + +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/MapView.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/MapView.java new file mode 100644 index 0000000..37a0fe8 --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/MapView.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm; + +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Bundle; +import android.util.AttributeSet; +import android.view.View; + +import com.car2go.maps.MapContainerView; +import com.car2go.maps.OnMapReadyCallback; + +import org.osmdroid.config.Configuration; +import org.osmdroid.tileprovider.tilesource.TileSourceFactory; +import org.osmdroid.views.CustomZoomButtonsController; + +/** + * @see MapContainerView + */ +public class MapView extends MapContainerView { + + private OsmMap anyMap; + + public MapView(Context context) { + super(context); + + initView(context, null); + } + + public MapView(Context context, AttributeSet attrs) { + super(context, attrs); + + initView(context, attrs); + } + + private void initView(Context context, AttributeSet attrs) { + org.osmdroid.views.MapView mapView = new org.osmdroid.views.MapView(context); + + addView(mapView); + + mapView.setTileSource(TileSourceFactory.MAPNIK); + mapView.setMultiTouchControls(true); + mapView.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER); + + anyMap = new OsmMap(mapView); + + setClipToPadding(false); + setLayerType(LAYER_TYPE_SOFTWARE, null); + + applyAttributes(context, attrs); + } + + private void applyAttributes(Context context, AttributeSet attrs) { + if (attrs == null) { + return; + } + + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MapView); + try { + boolean liteMode = typedArray.getBoolean(R.styleable.MapView_anyMapLiteMode, false); + + anyMap.getUiSettings().setAllGesturesEnabled(!liteMode); + } finally { + typedArray.recycle(); + } + } + + @Override + public void getMapAsync(final OnMapReadyCallback callback) { + post(new Runnable() { + @Override + public void run() { + callback.onMapReady(anyMap); + } + }); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + // Do nothing + } + + @Override + public void onResume() { + // Do nothing + } + + @Override + public void onPause() { + // Do nothing + } + + @Override + public void onDestroy() { + // Do nothing + } + + @Override + public void onLowMemory() { + // Do nothing + } + + @Override + public void onSaveInstanceState(Bundle outState) { + // Do nothing + } + + public void setUserAgent(String UA) { + Configuration.getInstance().setUserAgentValue(UA); + } + +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/MapsConfiguration.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/MapsConfiguration.java new file mode 100644 index 0000000..56119a5 --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/MapsConfiguration.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm; + +import android.content.Context; + +import com.car2go.maps.AnyMap; + +import java.util.Collections; +import java.util.Set; + +/** + * Initializer for OSM maps. + */ +public final class MapsConfiguration implements com.car2go.maps.MapsConfiguration { + + private static final MapsConfiguration instance = new MapsConfiguration(); + + private MapsConfiguration() { + } + + public static MapsConfiguration getInstance() { + return instance; + } + + @Override + public void initialize(Context context) { + BitmapDescriptorFactory.initialize(context); + } + + @Override + public Set getSupportedFeatures() { + return Collections.singleton(AnyMap.Feature.REVEALABLE); + } + +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/MyLocationHandler.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/MyLocationHandler.java new file mode 100644 index 0000000..8caa0dc --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/MyLocationHandler.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm; + +import com.car2go.maps.AnyMap; + +import org.osmdroid.views.MapView; +import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay; + +/** + * Handles MyLocation feature + */ +class MyLocationHandler { + + private final MyLocationNewOverlay myLocationOverlay; + + public MyLocationHandler(MapView map) { + myLocationOverlay = new MyLocationNewOverlay(map); + map.getOverlays().add(myLocationOverlay); + } + + /** + * @see AnyMap#setMyLocationEnabled(boolean) + */ + public void setMyLocationEnabled(boolean enabled) { + if (enabled) { + myLocationOverlay.enableMyLocation(); + } else { + myLocationOverlay.disableMyLocation(); + } + } + +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/OsmBitmapDescriptor.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/OsmBitmapDescriptor.java new file mode 100644 index 0000000..1bb41a4 --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/OsmBitmapDescriptor.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm; + +import android.graphics.Bitmap; + +import com.car2go.maps.model.BitmapDescriptor; + +/** + * Holds {@link android.graphics.Bitmap} for {@link com.car2go.maps.osm.drawable.OsmMarker} + */ +public class OsmBitmapDescriptor implements BitmapDescriptor { + + public final Bitmap bitmap; + + public OsmBitmapDescriptor(Bitmap bitmap) { + this.bitmap = bitmap; + } + +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/OsmCameraUpdate.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/OsmCameraUpdate.java new file mode 100644 index 0000000..4aeab47 --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/OsmCameraUpdate.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm; + +import com.car2go.maps.CameraUpdate; +import com.car2go.maps.model.LatLng; +import com.car2go.maps.model.LatLngBounds; + +/** + * Contains information about pending map camera update + */ +public class OsmCameraUpdate implements CameraUpdate { + + public final LatLng center; + public final Float zoom; + public final LatLngBounds bounds; + public final Integer padding; + + private OsmCameraUpdate(Builder builder) { + center = builder.center; + zoom = builder.zoom; + bounds = builder.bounds; + padding = builder.padding; + } + + /** + * Builder for {@link OsmCameraUpdate} + */ + public static final class Builder { + private LatLng center; + private Float zoom; + private LatLngBounds bounds; + private Integer padding; + + public Builder() { + } + + public Builder(OsmCameraUpdate copy) { + this.center = copy.center; + this.zoom = copy.zoom; + this.bounds = copy.bounds; + this.padding = copy.padding; + } + + /** + * Assigns new center of the camera. Can't be used together with {@link #bounds} + * + * @param center new center of the camera + * @return same {@link OsmCameraUpdate.Builder} + */ + public Builder center(LatLng center) { + this.center = center; + return this; + } + + /** + * Assigns new zoom level of the camera. Can't be used together with {@link #bounds} + * + * @param zoom new zoom level of the camera + * @return same {@link OsmCameraUpdate.Builder} + */ + public Builder zoom(Float zoom) { + this.zoom = zoom; + return this; + } + + /** + * Assigns new displayed bounds of the camera. Can't be used together with + * {@link #center(LatLng)} or {@link #zoom(Float)} + * + * @param bounds new bounds displayed by the camera + * @return same {@link OsmCameraUpdate.Builder} + */ + public Builder bounds(LatLngBounds bounds) { + this.bounds = bounds; + return this; + } + + /** + * Used in conjunction with {@link #bounds(LatLngBounds)} + * + * @param padding padding in pixels from the sides of the displayed bounds. + * @return same {@link OsmCameraUpdate.Builder} + */ + public Builder padding(Integer padding) { + this.padding = padding; + return this; + } + + /** + * @return new {@link OsmCameraUpdate} + */ + public OsmCameraUpdate build() { + if ((center != null || zoom != null) && bounds != null) { + throw new IllegalStateException("Conflicting parameters: center/zoom and bounds"); + } + + return new OsmCameraUpdate(this); + } + + } +} + diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/OsmMap.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/OsmMap.java new file mode 100644 index 0000000..bb17926 --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/OsmMap.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm; + +import android.content.Context; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import com.car2go.maps.AnyMap; +import com.car2go.maps.CameraUpdate; +import com.car2go.maps.Projection; +import com.car2go.maps.UiSettings; +import com.car2go.maps.model.CameraPosition; +import com.car2go.maps.model.Circle; +import com.car2go.maps.model.CircleOptions; +import com.car2go.maps.model.LatLng; +import com.car2go.maps.model.LatLngBounds; +import com.car2go.maps.model.Marker; +import com.car2go.maps.model.MarkerOptions; +import com.car2go.maps.model.Polygon; +import com.car2go.maps.model.PolygonOptions; +import com.car2go.maps.model.Polyline; +import com.car2go.maps.model.PolylineOptions; +import com.car2go.maps.model.VisibleRegion; +import com.car2go.maps.osm.drawable.DrawableComponentFactory; + +import org.osmdroid.api.IGeoPoint; +import org.osmdroid.config.Configuration; +import org.osmdroid.events.MapListener; +import org.osmdroid.events.ScrollEvent; +import org.osmdroid.events.ZoomEvent; +import org.osmdroid.views.MapView; + +import static com.car2go.maps.osm.util.OsmUtils.toLatLng; + +/** + * Implementation of {@link AnyMap} which works with Open Street Maps + */ +class OsmMap implements AnyMap { + + private final org.osmdroid.views.MapView map; + + private final CameraUpdateHandler cameraUpdateHandler; + private final MyLocationHandler myLocationHandler; + private final DrawableComponentFactory drawableComponentFactory; + private final UiSettings uiSettings; + + private OnMapClickListener onMapClickListener = OnMapClickListener.NULL; + private OnMapLongClickListener onMapLongClickListener = OnMapLongClickListener.NULL; + + private boolean mapEnabled = true; + + OsmMap(MapView map) { + this.map = map; + + cameraUpdateHandler = new CameraUpdateHandler(map); + myLocationHandler = new MyLocationHandler(map); + drawableComponentFactory = new DrawableComponentFactory(map); + uiSettings = new OsmUiSettings(); + + map.setOnTouchListener(new MapTouchListener(map.getContext())); + } + + @Override + public void moveCamera(CameraUpdate cameraUpdate) { + cameraUpdateHandler.moveCamera(cameraUpdate); + } + + @Override + public void animateCamera(CameraUpdate cameraUpdate) { + cameraUpdateHandler.animateCamera(cameraUpdate); + } + + @Override + public void animateCamera(CameraUpdate cameraUpdate, CancelableCallback callback) { + cameraUpdateHandler.animateCamera(cameraUpdate, callback); + } + + @Override + public void animateCamera(CameraUpdate cameraUpdate, int duration, CancelableCallback callback) { + cameraUpdateHandler.animateCamera(cameraUpdate, duration, callback); + } + + @Override + public CameraPosition getCameraPosition() { + return currentCameraPosition(); + } + + @Override + public Projection getProjection() { + org.osmdroid.views.Projection projection = map.getProjection(); + + return new OsmProjection( + new VisibleRegion( + new LatLngBounds( + new LatLng( + projection.getSouthWest().getLatitude(), + projection.getSouthWest().getLongitude() + ), + new LatLng( + projection.getNorthEast().getLatitude(), + projection.getNorthEast().getLongitude() + ) + ) + ) + ); + } + + @Override + public Marker addMarker(MarkerOptions options) { + return drawableComponentFactory.addMarker(options); + } + + @Override + public Circle addCircle(CircleOptions options) { + return drawableComponentFactory.addCircle(options); + } + + @Override + public Polygon addPolygon(PolygonOptions options) { + return drawableComponentFactory.addPolygon(options); + } + + @Override + public Polyline addPolyline(PolylineOptions options) { + return drawableComponentFactory.addPolyline(options); + } + + @Override + public UiSettings getUiSettings() { + return uiSettings; + } + + @Override + public void setOnMapClickListener(OnMapClickListener listener) { + onMapClickListener = (listener == null) + ? OnMapClickListener.NULL + : listener; + } + + @Override + public void setOnMapLongClickListener(OnMapLongClickListener listener) { + onMapLongClickListener = (listener == null) + ? OnMapLongClickListener.NULL + : listener; + } + + @Override + public void setOnCameraChangeListener(OnCameraChangeListener listener) { + map.setMapListener(new OsmMapListener(listener)); + } + + @Override + public void setOnMarkerClickListener(OnMarkerClickListener listener) { + drawableComponentFactory.setOnMarkerClickListener(listener); + } + + @Override + public void setInfoWindowAdapter(InfoWindowAdapter adapter) { + // Do nothing + } + + @Override + public void setTrafficEnabled(boolean enabled) { + // Do nothing + } + + @Override + public void setMyLocationEnabled(boolean enabled) { + myLocationHandler.setMyLocationEnabled(enabled); + } + + @Override + public void setMapType(Type type) { + // Do nothing + } + + @Override + public void setPadding(int left, int top, int right, int bottom) { + map.setTranslationY((top - bottom) / 2f); + map.setTranslationX((left - right) / 2f); + } + + @Override + public void onUserLocationChanged(LatLng location, float accuracy) { + //Do nothing + } + + private CameraPosition currentCameraPosition() { + final IGeoPoint center = map.getMapCenter(); + final int zoomLevel = map.getZoomLevel(); + + return new CameraPosition( + new LatLng(center.getLatitude(), center.getLongitude()), + zoomLevel + ); + } + + /** + * Detects clicks and long-clicks on map + */ + private class MapTouchListener implements View.OnTouchListener, GestureDetector.OnGestureListener { + + private GestureDetector gestureDetector; + + public MapTouchListener(Context context) { + gestureDetector = new GestureDetector(context, this); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (!mapEnabled) { + return true; + } + + gestureDetector.onTouchEvent(event); + + return false; + } + + @Override + public boolean onDown(MotionEvent e) { + return true; + } + + @Override + public void onShowPress(MotionEvent e) { + } + + @Override + public boolean onSingleTapUp(MotionEvent e) { + onMapClickListener.onMapClick(touchPoint(e)); + + return true; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + return false; + } + + @Override + public void onLongPress(MotionEvent e) { + onMapLongClickListener.onMapLongClick(touchPoint(e)); + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + return false; + } + + private LatLng touchPoint(MotionEvent e) { + IGeoPoint geoPoint = map.getProjection().fromPixels( + (int) e.getX(), + (int) e.getY() + ); + return toLatLng(geoPoint); + } + + } + + /** + * Listens for map position changes and delegates them + * to {@link com.car2go.maps.AnyMap.OnCameraChangeListener} + */ + private class OsmMapListener implements MapListener { + + private final OnCameraChangeListener listener; + + private OsmMapListener(OnCameraChangeListener listener) { + this.listener = listener; + } + + @Override + public boolean onScroll(ScrollEvent event) { + notifyListener(); + return false; + } + + @Override + public boolean onZoom(ZoomEvent event) { + notifyListener(); + return false; + } + + private void notifyListener() { + listener.onCameraChange( + currentCameraPosition() + ); + } + + } + + /** + * UI settings for OpenStreetMap + */ + private class OsmUiSettings implements UiSettings { + + @Override + public void setAllGesturesEnabled(final boolean enabled) { + mapEnabled = enabled; + } + + @Override + public void setMyLocationButtonEnabled(boolean enabled) { + // Do nothing + } + + @Override + public void setMapToolbarEnabled(boolean enabled) { + // Do nothing + } + } + +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/OsmProjection.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/OsmProjection.java new file mode 100644 index 0000000..50a5e8c --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/OsmProjection.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm; + +import com.car2go.maps.Projection; +import com.car2go.maps.model.VisibleRegion; + +/** + * Adapts OpenStreetMap projection to AnyMap projection + */ +public class OsmProjection implements Projection { + + private final VisibleRegion visibleRegion; + + public OsmProjection(VisibleRegion visibleRegion) { + this.visibleRegion = visibleRegion; + } + + @Override + public VisibleRegion getVisibleRegion() { + return visibleRegion; + } + +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/DrawableComponentFactory.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/DrawableComponentFactory.java new file mode 100644 index 0000000..7567320 --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/DrawableComponentFactory.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm.drawable; + +import com.car2go.maps.AnyMap; +import com.car2go.maps.model.Circle; +import com.car2go.maps.model.CircleOptions; +import com.car2go.maps.model.Marker; +import com.car2go.maps.model.MarkerOptions; +import com.car2go.maps.model.Polygon; +import com.car2go.maps.model.PolygonOptions; +import com.car2go.maps.model.Polyline; +import com.car2go.maps.model.PolylineOptions; +import com.car2go.maps.osm.drawable.overlay.MarkerOverlayItem; + +import org.osmdroid.views.MapView; +import org.osmdroid.views.overlay.ItemizedIconOverlay; + +import java.util.ArrayList; + +/** + * Creates {@link com.car2go.maps.model.DrawableComponent} and attaches them to maps + */ +public class DrawableComponentFactory { + + private final MapView map; + + private final ItemizedIconOverlay markersOverlay; + + private AnyMap.OnMarkerClickListener onMarkerClickListener = AnyMap.OnMarkerClickListener.NULL; + + public DrawableComponentFactory(MapView map) { + this.map = map; + + markersOverlay = new ItemizedIconOverlay<>( + map.getContext(), + new ArrayList(), + new ItemizedIconOverlay.OnItemGestureListener() { + @Override + public boolean onItemSingleTapUp(int index, MarkerOverlayItem item) { + return onMarkerClickListener.onMarkerClick(item.marker); + } + + @Override + public boolean onItemLongPress(int index, MarkerOverlayItem item) { + return false; + } + } + ); + + map.getOverlays().add(markersOverlay); + } + + /** + * Adds marker to the map + * + * @return added {@link Marker} which is bound to the map. + */ + public Marker addMarker(MarkerOptions options) { + return new OsmMarker(map, options, markersOverlay); + } + + /** + * Adds circle to the map + * + * @return added {@link Circle} which is bound to the map. + */ + public Circle addCircle(CircleOptions options) { + return new OsmCircle(map, options); + } + + /** + * Adds polygon to the map. + * + * @return added {@link Polygon} which is bound to the map + */ + public Polygon addPolygon(PolygonOptions options) { + return new OsmPolygon(map, options); + } + + /** + * Adds polyline to the map. + * + * @return added {@link Polyline} which is bound to the map + */ + public Polyline addPolyline(PolylineOptions options) { + return new OsmPolyline(map, options); + } + + /** + * @param listener listener which will be invoked on marker clicks + */ + public void setOnMarkerClickListener(AnyMap.OnMarkerClickListener listener) { + onMarkerClickListener = listener == null + ? AnyMap.OnMarkerClickListener.NULL + : listener; + } +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/OsmCircle.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/OsmCircle.java new file mode 100644 index 0000000..9f13a42 --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/OsmCircle.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm.drawable; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Point; + +import com.car2go.maps.model.Circle; +import com.car2go.maps.model.CircleOptions; + +import org.osmdroid.api.IGeoPoint; +import org.osmdroid.views.MapView; +import org.osmdroid.views.Projection; +import org.osmdroid.views.overlay.Overlay; + +import static com.car2go.maps.osm.util.OsmUtils.toGeoPoint; + +/** + * Draws circle on OpenStreetMaps. + * Associated with given {@link com.car2go.maps.AnyMap} object, so it should not be cached. + */ +public class OsmCircle implements Circle { + + private final MapView map; + private final CircleOverlay overlay; + + OsmCircle(MapView map, CircleOptions options) { + this.map = map; + + overlay = new CircleOverlay(map.getContext(), options); + map.getOverlays().add(overlay); + + map.invalidate(); + } + + @Override + public void setVisible(boolean visible) { + overlay.setEnabled(visible); + map.invalidate(); + } + + @Override + public void remove() { + map.getOverlays().remove(overlay); + map.invalidate(); + } + + /** + * Overlay which draws a single circle on OpenStreetMap + */ + private static class CircleOverlay extends Overlay { + + private final IGeoPoint position; + private final float radius; + + private final Paint fillPaint; + private final Paint strokePaint; + + private final Point auxPoint = new Point(); + + public CircleOverlay(Context context, CircleOptions options) { + super(context); + + position = toGeoPoint(options.getCenter()); + radius = (float) options.getRadius(); + + fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + fillPaint.setStyle(Paint.Style.FILL); + fillPaint.setColor(options.getFillColor()); + + strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + strokePaint.setStyle(Paint.Style.STROKE); + strokePaint.setColor(options.getStrokeColor()); + strokePaint.setStrokeWidth(options.getStrokeWidth()); + } + + @Override + public void draw(Canvas c, MapView osmv, boolean shadow) { + Projection projection = osmv.getProjection(); + + Point centerPoint = projection.toPixels(position, auxPoint); + float radiusPixels = projection.metersToPixels(radius); + + c.drawCircle( + centerPoint.x, + centerPoint.y, + radiusPixels, + fillPaint + ); + + c.drawCircle( + centerPoint.x, + centerPoint.y, + radiusPixels, + strokePaint + ); + } + + } +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/OsmMarker.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/OsmMarker.java new file mode 100644 index 0000000..2929198 --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/OsmMarker.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2016 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm.drawable; + +import android.graphics.drawable.BitmapDrawable; + +import com.car2go.maps.model.BitmapDescriptor; +import com.car2go.maps.model.LatLng; +import com.car2go.maps.model.Marker; +import com.car2go.maps.model.MarkerOptions; +import com.car2go.maps.osm.OsmBitmapDescriptor; +import com.car2go.maps.osm.drawable.overlay.MarkerOverlayItem; +import com.car2go.maps.osm.util.OsmUtils; + +import org.osmdroid.views.MapView; +import org.osmdroid.views.overlay.ItemizedIconOverlay; + +import static com.car2go.maps.osm.util.OsmUtils.anchorToHotspot; + +/** + * Draws marker on OpenStreetMaps. + * Associated with given {@link com.car2go.maps.AnyMap} object, so it should not be cached. + */ +public class OsmMarker implements Marker { + + private final MapView map; + + private final LatLng position; + private final ItemizedIconOverlay overlay; + private final MarkerOverlayItem overlayItem; + + private boolean removed = false; + private boolean visible = true; + + OsmMarker(MapView map, MarkerOptions options, ItemizedIconOverlay markersOverlay) { + this.map = map; + + overlayItem = new MarkerOverlayItem( + OsmUtils.toGeoPoint(options.getPosition()), + this + ); + + OsmBitmapDescriptor descriptor = (OsmBitmapDescriptor) options.getIcon(); + overlayItem.setMarker( + new BitmapDrawable(map.getResources(), descriptor.bitmap) + ); + overlayItem.setMarkerHotspot( + anchorToHotspot(options.getAnchorU(), options.getAnchorV()) + ); + + markersOverlay.addItem(overlayItem); + + overlay = markersOverlay; + position = options.getPosition(); + + map.invalidate(); + } + + @Override + public void setIcon(BitmapDescriptor icon) { + OsmBitmapDescriptor descriptor = (OsmBitmapDescriptor) icon; + + overlayItem.setMarker(new BitmapDrawable(map.getResources(), descriptor.bitmap)); + map.invalidate(); + } + + @Override + public LatLng getPosition() { + return position; + } + + @Override + public void showInfoWindow() { + // Do nothing + } + + @Override + public void setRotation(float rotation) { + // Do nothing + } + + @Override + public void setVisible(boolean visible) { + if (removed || this.visible == visible) { + return; + } + + if (visible) { + overlay.addItem(overlayItem); + } else { + overlay.removeItem(overlayItem); + } + + this.visible = visible; + + map.invalidate(); + } + + @Override + public void remove() { + if (!visible) { + removed = true; + } + + if (removed) { + return; + } + + overlay.removeItem(overlayItem); + removed = true; + visible = false; + + map.invalidate(); + } + + @Override + public void setZ(int z) { + // Do nothing + } +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/OsmPolygon.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/OsmPolygon.java new file mode 100644 index 0000000..d4df588 --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/OsmPolygon.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm.drawable; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.Region; + +import com.car2go.maps.model.LatLng; +import com.car2go.maps.model.Polygon; +import com.car2go.maps.model.PolygonOptions; + +import org.osmdroid.api.IGeoPoint; +import org.osmdroid.views.MapView; +import org.osmdroid.views.Projection; +import org.osmdroid.views.overlay.Overlay; + +import java.util.ArrayList; +import java.util.List; + +import static com.car2go.maps.osm.util.OsmUtils.toGeoPoints; + +/** + * Draws polygon on OpenStreetMaps. + * Associated with given {@link com.car2go.maps.AnyMap} object, so it should not be cached. + */ +public class OsmPolygon implements Polygon { + + private final MapView map; + private final PolygonOverlay overlay; + private final PolygonOptions options; + + OsmPolygon(MapView map, PolygonOptions options) { + this.map = map; + this.options = options; + + overlay = new PolygonOverlay(map, options); + + map.getOverlays().add(0, overlay); + + map.invalidate(); + } + + @Override + public void setHoles(List> holes) { + overlay.setHoles(holes); + } + + @Override + public List getPoints() { + return options.getPoints(); + } + + @Override + public void setVisible(boolean visible) { + overlay.setEnabled(visible); + map.invalidate(); + } + + @Override + public void remove() { + map.getOverlays().remove(overlay); + map.invalidate(); + } + + /** + * Overlay which draws a single polygon (with holes) on OpenStreetMap + */ + private static class PolygonOverlay extends Overlay { + + private final MapView map; + + private final boolean outsider; + + private final List points; + private final List> holes = new ArrayList<>(); + + private final Path polygonPath = new Path(); + private final Paint fillPaint; + private final Paint strokePaint; + + private final Point auxPoint = new Point(); + + public PolygonOverlay(MapView map, PolygonOptions options) { + super(map.getContext()); + + this.map = map; + + outsider = options.isOutsider(); + points = toGeoPoints(options.getPoints()); + + fillPaint = new Paint(); + fillPaint.setStyle(Paint.Style.FILL); + fillPaint.setColor(options.getFillColor()); + + fillPaint.setAlpha( + Color.alpha(options.getFillColor()) / 2 + ); + + strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + strokePaint.setStyle(Paint.Style.STROKE); + strokePaint.setColor(options.getStrokeColor()); + strokePaint.setStrokeWidth(options.getStrokeWidth()); + } + + void setHoles(List> holes) { + this.holes.clear(); + + for (List hole : holes) { + this.holes.add( + toGeoPoints(hole) + ); + } + + map.invalidate(); + } + + @Override + public void draw(Canvas c, MapView osmv, boolean shadow) { + Projection projection = osmv.getProjection(); + + c.save(); + + if (!holes.isEmpty()) { + polygonPath.rewind(); + populateHoles(projection); + + // Draw outline of the holes + c.drawPath(polygonPath, strokePaint); + + // Clip holes so that when we'll draw polygon on top of them the area under the + // holes will be "cropped" + c.clipPath(polygonPath, Region.Op.DIFFERENCE); + } + + if (!points.isEmpty()) { + if (!outsider) { + polygonPath.rewind(); + populateOutline(projection); + + // Draw and fill polygon itself + c.drawPath(polygonPath, fillPaint); + c.drawPath(polygonPath, strokePaint); + } else { + // Optimization for outsider-polygons. Just fill the whole screen with the color. + // We cropped holes before, so it will appear as "transparent" polygons are + // drawn. + c.drawPaint(fillPaint); + } + } + + c.restore(); + } + + private void populateOutline(Projection projection) { + populatePath(projection, points, polygonPath); + } + + private void populateHoles(Projection projection) { + for (List hole : holes) { + populatePath(projection, hole, polygonPath); + } + } + + private void populatePath(Projection projection, List points, Path path) { + Point startPoint = projection.toPixels(points.get(0), auxPoint); + path.moveTo(startPoint.x, startPoint.y); + + for (int i = 1; i < points.size(); i++) { + Point point = projection.toPixels(points.get(i), auxPoint); + + path.lineTo(point.x, point.y); + } + + path.close(); + } + + } +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/OsmPolyline.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/OsmPolyline.java new file mode 100644 index 0000000..7bad194 --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/OsmPolyline.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm.drawable; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Point; + +import com.car2go.maps.model.Polyline; +import com.car2go.maps.model.PolylineOptions; + +import org.osmdroid.api.IGeoPoint; +import org.osmdroid.views.MapView; +import org.osmdroid.views.Projection; +import org.osmdroid.views.overlay.Overlay; + +import java.util.List; + +import static com.car2go.maps.osm.util.OsmUtils.toGeoPoints; + +/** + * Draws polyline on OpenStreetMaps. + * Associated with given {@link com.car2go.maps.AnyMap} object, so it should not be cached. + */ +public class OsmPolyline implements Polyline { + + private final MapView map; + private final PolylineOverlay overlay; + + public OsmPolyline(MapView map, PolylineOptions options) { + this.map = map; + + overlay = new PolylineOverlay(map.getContext(), options); + map.getOverlays().add(overlay); + + map.invalidate(); + } + + @Override + public void setVisible(boolean visible) { + overlay.setEnabled(visible); + map.invalidate(); + } + + @Override + public void remove() { + map.getOverlays().remove(overlay); + map.invalidate(); + } + + /** + * Overlay which draws a single polyline on OpenStreetMap + */ + private static class PolylineOverlay extends Overlay { + + private static final int ITEMS_PER_LINE = 4; + + private final List geoPoints; + private final float[] linePoints; + + private final Paint linePaint; + + private final Point auxPoint = new Point(); + + public PolylineOverlay(Context ctx, PolylineOptions options) { + super(ctx); + + geoPoints = toGeoPoints(options.getPoints()); + linePoints = new float[(geoPoints.size() - 1) * ITEMS_PER_LINE]; + + linePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + linePaint.setStyle(Paint.Style.STROKE); + linePaint.setStrokeWidth(options.getWidth()); + linePaint.setStrokeCap(Paint.Cap.ROUND); + linePaint.setColor(options.getColor()); + } + + @Override + public void draw(Canvas c, MapView osmv, boolean shadow) { + Projection projection = osmv.getProjection(); + + prepareLines(projection); + + c.drawLines(linePoints, linePaint); + } + + private void prepareLines(Projection projection) { + for (int i = 0; i < geoPoints.size() - 1; i++) { + projection.toPixels(geoPoints.get(i), auxPoint); + + linePoints[ITEMS_PER_LINE * i] = auxPoint.x; + linePoints[ITEMS_PER_LINE * i + 1] = auxPoint.y; + + projection.toPixels(geoPoints.get(i + 1), auxPoint); + + linePoints[ITEMS_PER_LINE * i + 2] = auxPoint.x; + linePoints[ITEMS_PER_LINE * i + 3] = auxPoint.y; + } + } + + } + +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/overlay/MarkerOverlayItem.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/overlay/MarkerOverlayItem.java new file mode 100644 index 0000000..8465614 --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/drawable/overlay/MarkerOverlayItem.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm.drawable.overlay; + +import com.car2go.maps.model.Marker; + +import org.osmdroid.api.IGeoPoint; +import org.osmdroid.views.overlay.OverlayItem; + +/** + * Overlay item for {@link org.osmdroid.views.overlay.ItemizedIconOverlay} which is associated + * with {@link Marker} + */ +public class MarkerOverlayItem extends OverlayItem { + + public final Marker marker; + + /** + * @param geoPoint position of the item + * @param marker associated {@link Marker} + */ + public MarkerOverlayItem(IGeoPoint geoPoint, Marker marker) { + super(null, null, geoPoint); + + this.marker = marker; + } + +} diff --git a/anymaps-osm/src/main/java/com/car2go/maps/osm/util/OsmUtils.java b/anymaps-osm/src/main/java/com/car2go/maps/osm/util/OsmUtils.java new file mode 100644 index 0000000..188d983 --- /dev/null +++ b/anymaps-osm/src/main/java/com/car2go/maps/osm/util/OsmUtils.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm.util; + +import com.car2go.maps.model.LatLng; + +import org.osmdroid.api.IGeoPoint; +import org.osmdroid.util.GeoPoint; +import org.osmdroid.views.overlay.OverlayItem; + +import java.util.ArrayList; +import java.util.List; + +/** + * OpenStreetMaps related utilities + */ +public class OsmUtils { + + private static final float ANCHOR_LOW_THRESHOLD = 0.2f; + private static final float ANCHOR_HIGH_THRESHOLD = 0.7f; + + /** + * @return {@link IGeoPoint} with same coordinates as {@link LatLng} + */ + public static IGeoPoint toGeoPoint(LatLng latLng) { + return new GeoPoint( + latLng.latitude, + latLng.longitude + ); + } + + /** + * Convenience for {@link #toGeoPoint(LatLng)} which works on {@link List} + */ + public static List toGeoPoints(List points) { + List result = new ArrayList<>(); + + for (LatLng point : points) { + result.add( + toGeoPoint(point) + ); + } + + return result; + } + + /** + * @return {@link LatLng} with same coordinates as {@link IGeoPoint} + */ + public static LatLng toLatLng(IGeoPoint geoPoint) { + return new LatLng( + geoPoint.getLatitude(), + geoPoint.getLongitude() + ); + } + + /** + * Since OpenStreetMaps does not support UV coordinates for anchors there is a need for + * explicit conversion. This method searches for + * {@link org.osmdroid.views.overlay.OverlayItem.HotspotPlace} which is closest to given UV + * coordinates. + * + * @return {@link org.osmdroid.views.overlay.OverlayItem.HotspotPlace} which is closest to + * given UV coordinates. + */ + public static OverlayItem.HotspotPlace anchorToHotspot(float u, float v) { + if (v > ANCHOR_HIGH_THRESHOLD) { + if (u < ANCHOR_LOW_THRESHOLD) { + return OverlayItem.HotspotPlace.LOWER_LEFT_CORNER; + } else if (u > ANCHOR_HIGH_THRESHOLD) { + return OverlayItem.HotspotPlace.LOWER_RIGHT_CORNER; + } else { + return OverlayItem.HotspotPlace.BOTTOM_CENTER; + } + } else if (v < ANCHOR_LOW_THRESHOLD) { + if (u < ANCHOR_LOW_THRESHOLD) { + return OverlayItem.HotspotPlace.UPPER_LEFT_CORNER; + } else if (u > ANCHOR_HIGH_THRESHOLD) { + return OverlayItem.HotspotPlace.UPPER_RIGHT_CORNER; + } else { + return OverlayItem.HotspotPlace.TOP_CENTER; + } + } else { + if (u < ANCHOR_LOW_THRESHOLD) { + return OverlayItem.HotspotPlace.LEFT_CENTER; + } else if (u > ANCHOR_HIGH_THRESHOLD) { + return OverlayItem.HotspotPlace.RIGHT_CENTER; + } else { + return OverlayItem.HotspotPlace.CENTER; + } + } + } + +} diff --git a/anymaps-osm/src/test/java/com/car2go/maps/osm/CameraUpdateHandlerTest.java b/anymaps-osm/src/test/java/com/car2go/maps/osm/CameraUpdateHandlerTest.java new file mode 100644 index 0000000..de71867 --- /dev/null +++ b/anymaps-osm/src/test/java/com/car2go/maps/osm/CameraUpdateHandlerTest.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm; + +import com.car2go.maps.AnyMap; +import com.car2go.maps.model.LatLng; +import com.car2go.maps.model.LatLngBounds; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.osmdroid.api.IMapController; + +import static com.car2go.maps.osm.util.OsmUtils.toGeoPoint; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.refEq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +public class CameraUpdateHandlerTest { + + @Mock + org.osmdroid.views.MapView map; + @Mock + IMapController mapController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + doReturn(mapController) + .when(map) + .getController(); + } + + @Test + public void testMoveCameraToPosition() throws Exception { + // Given + CameraUpdateHandler handler = new CameraUpdateHandler(map); + + final LatLng center = new LatLng(10, 10); + + OsmCameraUpdate cameraUpdate = new OsmCameraUpdate.Builder() + .center(center) + .build(); + + // When + handler.moveCamera(cameraUpdate); + + // Then + verify(mapController).setCenter(eq(toGeoPoint(center))); + verifyNoMoreInteractions(mapController); + } + + @Test + public void testMoveCameraToPositionWithZoom() throws Exception { + // Given + CameraUpdateHandler handler = new CameraUpdateHandler(map); + + final LatLng center = new LatLng(10, 10); + final int zoomLevel = 10; + + OsmCameraUpdate cameraUpdate = new OsmCameraUpdate.Builder() + .center(center) + .zoom((float) zoomLevel) + .build(); + + // When + handler.moveCamera(cameraUpdate); + + // Then + verify(mapController).setCenter(eq(toGeoPoint(center))); + verify(mapController).setZoom(zoomLevel); + verifyNoMoreInteractions(mapController); + } + + @Test + public void testMoveCameraToBounds() throws Exception { + // Given + CameraUpdateHandler handler = new CameraUpdateHandler(map); + + final LatLngBounds bounds = new LatLngBounds( + new LatLng(10, 10), + new LatLng(20, 20) + ); + + OsmCameraUpdate cameraUpdate = new OsmCameraUpdate.Builder() + .bounds(bounds) + .build(); + + // When + handler.moveCamera(cameraUpdate); + + // Then + verify(mapController).setCenter(eq( + toGeoPoint(bounds.getCenter()) + )); + verify(mapController).zoomToSpan( + eq((int) (10 * 1e6)), + eq((int) (10 * 1e6)) + ); + } + + @Test + public void testAnimateCamera() throws Exception { + // Given + CameraUpdateHandler handler = spy(new CameraUpdateHandler(map)); + + final LatLng center = new LatLng(10, 10); + + OsmCameraUpdate cameraUpdate = new OsmCameraUpdate.Builder() + .center(center) + .build(); + + // When + handler.animateCamera(cameraUpdate); + + // Then + verify(handler).moveCamera(refEq(cameraUpdate)); + } + + @Test + public void testAnimateCameraWithCallback() throws Exception { + // Given + CameraUpdateHandler handler = spy(new CameraUpdateHandler(map)); + + final LatLng center = new LatLng(10, 10); + + OsmCameraUpdate cameraUpdate = new OsmCameraUpdate.Builder() + .center(center) + .build(); + + AnyMap.CancelableCallback callback = mock(AnyMap.CancelableCallback.class); + + // When + handler.animateCamera(cameraUpdate, callback); + + // Then + verify(handler).moveCamera(refEq(cameraUpdate)); + + verify(callback).onFinish(); + verifyNoMoreInteractions(callback); + } + +} \ No newline at end of file diff --git a/anymaps-osm/src/test/java/com/car2go/maps/osm/util/OsmUtilsTest.java b/anymaps-osm/src/test/java/com/car2go/maps/osm/util/OsmUtilsTest.java new file mode 100644 index 0000000..e9dc78e --- /dev/null +++ b/anymaps-osm/src/test/java/com/car2go/maps/osm/util/OsmUtilsTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2015 Daimler AG / Moovel GmbH + * + * All rights reserved + */ + +package com.car2go.maps.osm.util; + +import com.car2go.maps.model.LatLng; + +import org.junit.Test; +import org.osmdroid.api.IGeoPoint; +import org.osmdroid.util.GeoPoint; +import org.osmdroid.views.overlay.OverlayItem; + +import java.util.List; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +public class OsmUtilsTest { + + @Test + public void testToGeoPoint() throws Exception { + // Given + final LatLng input = new LatLng(10d, 20d); + + final GeoPoint expected = new GeoPoint(10d, 20d); + + // When + IGeoPoint output = OsmUtils.toGeoPoint(input); + + // Then + assertEquals(expected, output); + } + + @Test + public void testToGeoPoints() throws Exception { + // Given + final List input = asList( + new LatLng(10d, 20d), + new LatLng(30d, 40d) + ); + + final List expected = asList( + new GeoPoint(10d, 20d), + new GeoPoint(30d, 40d) + ); + + // When + List output = OsmUtils.toGeoPoints(input); + + // Then + assertEquals(expected, output); + } + + @Test + public void testToLatLng() throws Exception { + // Given + final GeoPoint input = new GeoPoint(10d, 20d); + + final LatLng expected = new LatLng(10d, 20d); + + // When + LatLng output = OsmUtils.toLatLng(input); + + // Then + assertEquals(expected, output); + } + + @Test + public void testAnchorToHotspot() throws Exception { + assertEquals( + OverlayItem.HotspotPlace.UPPER_LEFT_CORNER, + OsmUtils.anchorToHotspot(0f, 0f) + ); + + assertEquals( + OverlayItem.HotspotPlace.TOP_CENTER, + OsmUtils.anchorToHotspot(0.5f, 0f) + ); + + assertEquals( + OverlayItem.HotspotPlace.UPPER_RIGHT_CORNER, + OsmUtils.anchorToHotspot(1f, 0f) + ); + + assertEquals( + OverlayItem.HotspotPlace.LEFT_CENTER, + OsmUtils.anchorToHotspot(0f, 0.5f) + ); + + assertEquals( + OverlayItem.HotspotPlace.CENTER, + OsmUtils.anchorToHotspot(0.5f, 0.5f) + ); + + assertEquals( + OverlayItem.HotspotPlace.RIGHT_CENTER, + OsmUtils.anchorToHotspot(1f, 0.5f) + ); + + assertEquals( + OverlayItem.HotspotPlace.LOWER_LEFT_CORNER, + OsmUtils.anchorToHotspot(0f, 1f) + ); + + assertEquals( + OverlayItem.HotspotPlace.BOTTOM_CENTER, + OsmUtils.anchorToHotspot(0.5f, 1f) + ); + assertEquals( + OverlayItem.HotspotPlace.LOWER_RIGHT_CORNER, + OsmUtils.anchorToHotspot(1f, 1f) + ); + + } + +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index e7b4def..d10ffad 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app' +include ':app', ':anymaps-base', ':anymaps-osm'