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'