Merge branch 'release-agua' into master

# Conflicts:
#	circle.yml
#	cmake/core-files.cmake
#	platform/android/CHANGELOG.md
#	platform/android/MapboxGLAndroidSDK/build.gradle
#	platform/android/MapboxGLAndroidSDK/gradle.properties
#	platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java
#	platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
#	platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java
#	platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/text/LocalGlyphRasterizer.java
#	platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/BitmapUtils.java
#	platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
#	platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/maplayout/DebugModeActivity.java
#	platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/maplayout/LocalGlyphActivity.java
#	platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/userlocation/MyLocationTintActivity.java
#	platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_local_glyph.xml
#	platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
#	platform/darwin/src/MGLMapSnapshotter.mm
#	platform/darwin/src/MGLRendererConfiguration.h
#	platform/ios/CHANGELOG.md
#	platform/ios/DEVELOPING.md
#	platform/ios/INSTALL.md
#	platform/ios/README.md
#	platform/ios/resources/bg.lproj/Localizable.strings
#	platform/ios/resources/es.lproj/Localizable.strings
#	platform/ios/resources/vi.lproj/Localizable.strings
#	platform/ios/src/MGLMapView.mm
#	platform/macos/macos.xcodeproj/project.pbxproj
#	src/mbgl/text/glyph_manager.cpp
This commit is contained in:
Fabian Guerra
2018-01-04 12:55:19 -06:00
125 changed files with 3968 additions and 678 deletions

View File

@@ -0,0 +1,59 @@
package com.mapbox.mapboxsdk.attribution;
public class Attribution {
private static final String OPENSTREETMAP = "OpenStreetMap";
private static final String OPENSTREETMAP_ABBR = "OSM";
static final String TELEMETRY = "Telemetry Settings";
static final String IMPROVE_MAP_URL = "https://www.mapbox.com/map-feedback/";
static final String MAPBOX_URL = "https://www.mapbox.com/about/maps/";
static final String TELEMETRY_URL = "https://www.mapbox.com/telemetry/";
private String title;
private String url;
Attribution(String title, String url) {
this.title = title;
this.url = url;
}
public String getTitle() {
return title;
}
public String getTitleAbbreviated() {
if (title.equals(OPENSTREETMAP)) {
return OPENSTREETMAP_ABBR;
}
return title;
}
public String getUrl() {
return url;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Attribution that = (Attribution) o;
if (title != null ? !title.equals(that.title) : that.title != null) {
return false;
}
return url != null ? url.equals(that.url) : that.url == null;
}
@Override
public int hashCode() {
int result = title != null ? title.hashCode() : 0;
result = 31 * result + (url != null ? url.hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,62 @@
package com.mapbox.mapboxsdk.attribution;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.support.annotation.Nullable;
public class AttributionLayout {
private Bitmap logo;
private PointF anchorPoint;
private boolean shortText;
public AttributionLayout(@Nullable Bitmap logo, @Nullable PointF anchorPoint, boolean shortText) {
this.logo = logo;
this.anchorPoint = anchorPoint;
this.shortText = shortText;
}
public Bitmap getLogo() {
return logo;
}
public PointF getAnchorPoint() {
return anchorPoint;
}
public boolean isShortText() {
return shortText;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
AttributionLayout that = (AttributionLayout) o;
if (logo != null ? !logo.equals(that.logo) : that.logo != null) {
return false;
}
return anchorPoint != null ? anchorPoint.equals(that.anchorPoint) : that.anchorPoint == null;
}
@Override
public int hashCode() {
int result = logo != null ? logo.hashCode() : 0;
result = 31 * result + (anchorPoint != null ? anchorPoint.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "AttributionLayout{"
+ "logo=" + logo
+ ", anchorPoint=" + anchorPoint
+ '}';
}
}

View File

@@ -0,0 +1,230 @@
package com.mapbox.mapboxsdk.attribution;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.widget.TextView;
import java.util.Arrays;
import java.util.List;
public class AttributionMeasure {
private Bitmap logo;
private Bitmap logoSmall;
private Bitmap snapshot;
private TextView textView;
private TextView textViewShort;
private float margin;
private boolean shorterText;
AttributionMeasure(Bitmap snapshot, Bitmap logo, Bitmap logoSmall, TextView tv, TextView tvShort, float margin) {
this.snapshot = snapshot;
this.logo = logo;
this.logoSmall = logoSmall;
this.textView = tv;
this.textViewShort = tvShort;
this.margin = margin;
}
public AttributionLayout measure() {
Chain chain = new Chain(
new FullLogoLongTextCommand(),
new FullLogoShortTextCommand(),
new SmallLogoLongTextCommand(),
new SmallLogoShortTextCommand(),
new LongTextCommand(),
new ShortTextCommand(),
new NoTextCommand()
);
AttributionLayout attributionLayout = chain.start(this);
shorterText = attributionLayout.isShortText();
return attributionLayout;
}
private static class FullLogoLongTextCommand implements Command {
public AttributionLayout execute(AttributionMeasure measure) {
float width = measure.getLogoContainerWidth() + measure.getTextViewContainerWidth();
boolean fitBounds = width <= measure.getMaxSize();
if (fitBounds) {
PointF anchor = calculateAnchor(measure.snapshot, measure.textView, measure.margin);
return new AttributionLayout(measure.logo, anchor, false);
}
return null;
}
}
private static class FullLogoShortTextCommand implements Command {
@Override
public AttributionLayout execute(AttributionMeasure measure) {
float width = measure.getLogoContainerWidth() + measure.getTextViewShortContainerWidth();
boolean fitBounds = width <= measure.getMaxSizeShort();
if (fitBounds) {
PointF anchor = calculateAnchor(measure.snapshot, measure.textViewShort, measure.margin);
return new AttributionLayout(measure.logo, anchor, true);
}
return null;
}
}
private static class SmallLogoLongTextCommand implements Command {
@Override
public AttributionLayout execute(AttributionMeasure measure) {
float width = measure.getLogoSmallContainerWidth() + measure.getTextViewContainerWidth();
boolean fitBounds = width <= measure.getMaxSize();
if (fitBounds) {
PointF anchor = calculateAnchor(measure.snapshot, measure.textView, measure.margin);
return new AttributionLayout(measure.logoSmall, anchor, false);
}
return null;
}
}
private static class SmallLogoShortTextCommand implements Command {
@Override
public AttributionLayout execute(AttributionMeasure measure) {
float width = measure.getLogoContainerWidth() + measure.getTextViewShortContainerWidth();
boolean fitBounds = width <= measure.getMaxSizeShort();
if (fitBounds) {
PointF anchor = calculateAnchor(measure.snapshot, measure.textViewShort, measure.margin);
return new AttributionLayout(measure.logoSmall, anchor, true);
}
return null;
}
}
private static class LongTextCommand implements Command {
@Override
public AttributionLayout execute(AttributionMeasure measure) {
float width = measure.getTextViewContainerWidth() + measure.margin;
boolean fitBounds = width <= measure.getMaxSize();
if (fitBounds) {
return new AttributionLayout(null, calculateAnchor(measure.snapshot, measure.textView, measure.margin), false);
}
return null;
}
}
private static class ShortTextCommand implements Command {
@Override
public AttributionLayout execute(AttributionMeasure measure) {
float width = measure.getTextViewShortContainerWidth() + measure.margin;
boolean fitBounds = width <= measure.getMaxSizeShort();
if (fitBounds) {
PointF anchor = calculateAnchor(measure.snapshot, measure.textViewShort, measure.margin);
return new AttributionLayout(null, anchor, true);
}
return null;
}
}
private static class NoTextCommand implements Command {
@Override
public AttributionLayout execute(AttributionMeasure measure) {
return new AttributionLayout(null, null, false);
}
}
private static PointF calculateAnchor(Bitmap snapshot, TextView textView, float margin) {
return new PointF(
snapshot.getWidth() - textView.getMeasuredWidth() - margin,
snapshot.getHeight() - margin - textView.getMeasuredHeight()
);
}
public TextView getTextView() {
return shorterText ? textViewShort : textView;
}
private class Chain {
public List<Command> commands;
Chain(Command... commands) {
this.commands = Arrays.asList(commands);
}
public AttributionLayout start(AttributionMeasure measure) {
AttributionLayout attributionLayout = null;
for (Command command : commands) {
attributionLayout = command.execute(measure);
if (attributionLayout != null) {
break;
}
}
return attributionLayout;
}
}
public interface Command {
AttributionLayout execute(AttributionMeasure measure);
}
private float getTextViewContainerWidth() {
return textView.getMeasuredWidth() + margin;
}
private float getLogoContainerWidth() {
return logo.getWidth() + (2 * margin);
}
private float getTextViewShortContainerWidth() {
return textViewShort.getMeasuredWidth() + margin;
}
private float getLogoSmallContainerWidth() {
return logoSmall.getWidth() + (2 * margin);
}
private float getMaxSize() {
return snapshot.getWidth() * 8 / 10;
}
private float getMaxSizeShort() {
return snapshot.getWidth();
}
public static class Builder {
private Bitmap snapshot;
private Bitmap logo;
private Bitmap logoSmall;
private TextView textView;
private TextView textViewShort;
private float marginPadding;
public Builder setSnapshot(Bitmap snapshot) {
this.snapshot = snapshot;
return this;
}
public Builder setLogo(Bitmap logo) {
this.logo = logo;
return this;
}
public Builder setLogoSmall(Bitmap logoSmall) {
this.logoSmall = logoSmall;
return this;
}
public Builder setTextView(TextView textView) {
this.textView = textView;
return this;
}
public Builder setTextViewShort(TextView textViewShort) {
this.textViewShort = textViewShort;
return this;
}
public Builder setMarginPadding(float marginPadding) {
this.marginPadding = marginPadding;
return this;
}
public AttributionMeasure build() {
return new AttributionMeasure(snapshot, logo, logoSmall, textView, textViewShort, marginPadding);
}
}
}

View File

@@ -0,0 +1,257 @@
package com.mapbox.mapboxsdk.attribution;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.URLSpan;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Responsible for parsing attribution data coming from Sources and MapSnapshot.
* <p>
* Exposes multiple configuration options to manipulate data being parsed.
* Use the Options object to build these configurations.
* </p>
*/
public class AttributionParser {
private final Set<Attribution> attributions = new LinkedHashSet<>();
private final String attributionData;
private final boolean withImproveMap;
private final boolean withCopyrightSign;
private final boolean withTelemetryAttribution;
private final boolean withMapboxAttribution;
AttributionParser(String attributionData, boolean withImproveMap, boolean withCopyrightSign,
boolean withTelemetryAttribution, boolean withMapboxAttribution) {
this.attributionData = attributionData;
this.withImproveMap = withImproveMap;
this.withCopyrightSign = withCopyrightSign;
this.withTelemetryAttribution = withTelemetryAttribution;
this.withMapboxAttribution = withMapboxAttribution;
}
/**
* Get parsed attributions.
*
* @return the attributions
*/
public Set<Attribution> getAttributions() {
return attributions;
}
/**
* Get parsed attribution string.
*
* @return the parsed attribution string
*/
public String createAttributionString() {
return createAttributionString(false);
}
/**
* Get parsed attribution string.
*
* @param shortenedOutput if attribution string should contain shortened output
* @return the parsed attribution string
*/
public String createAttributionString(boolean shortenedOutput) {
StringBuilder stringBuilder = new StringBuilder(withCopyrightSign ? "" : "© ");
int counter = 0;
for (Attribution attribution : attributions) {
counter++;
stringBuilder.append(!shortenedOutput ? attribution.getTitle() : attribution.getTitleAbbreviated());
if (counter != attributions.size()) {
stringBuilder.append(" / ");
}
}
return stringBuilder.toString();
}
/**
* Main attribution for configuration
*/
protected void parse() {
parseAttributions();
addAdditionalAttributions();
}
/**
* Parse attributions
*/
private void parseAttributions() {
SpannableStringBuilder htmlBuilder = (SpannableStringBuilder) fromHtml(attributionData);
URLSpan[] urlSpans = htmlBuilder.getSpans(0, htmlBuilder.length(), URLSpan.class);
for (URLSpan urlSpan : urlSpans) {
parseUrlSpan(htmlBuilder, urlSpan);
}
}
/**
* Parse an URLSpan containing an attribution.
*
* @param htmlBuilder the html builder
* @param urlSpan the url span to be parsed
*/
private void parseUrlSpan(SpannableStringBuilder htmlBuilder, URLSpan urlSpan) {
String url = urlSpan.getURL();
if (isUrlValid(url)) {
String anchor = parseAnchorValue(htmlBuilder, urlSpan);
attributions.add(new Attribution(anchor, url));
}
}
/**
* Invoked to validate if an url is valid to be included in the final attribution.
*
* @param url the url to be validated
* @return if the url is valid
*/
private boolean isUrlValid(String url) {
return isValidForImproveThisMap(url) && isValidForMapbox(url);
}
/**
* Invoked to validate if an url is valid for the improve map configuration.
*
* @param url the url to be validated
* @return if the url is valid for improve this map
*/
private boolean isValidForImproveThisMap(String url) {
return withImproveMap || !url.equals(Attribution.IMPROVE_MAP_URL);
}
/**
* Invoked to validate if an url is valid for the Mapbox configuration.
*
* @param url the url to be validated
* @return if the url is valid for Mapbox
*/
private boolean isValidForMapbox(String url) {
return withMapboxAttribution || !url.equals(Attribution.MAPBOX_URL);
}
/**
* Parse the attribution by parsing the anchor value of html href tag.
*
* @param htmlBuilder the html builder
* @param urlSpan the current urlSpan
* @return the parsed anchor value
*/
private String parseAnchorValue(SpannableStringBuilder htmlBuilder, URLSpan urlSpan) {
int start = htmlBuilder.getSpanStart(urlSpan);
int end = htmlBuilder.getSpanEnd(urlSpan);
int length = end - start;
char[] charKey = new char[length];
htmlBuilder.getChars(start, end, charKey, 0);
return stripCopyright(String.valueOf(charKey));
}
/**
* Utility to strip the copyright sign from an attribution
*
* @param anchor the attribution string to strip
* @return the stripped attribution string without the copyright sign
*/
private String stripCopyright(String anchor) {
if (!withCopyrightSign && anchor.startsWith("© ")) {
anchor = anchor.substring(2, anchor.length());
}
return anchor;
}
/**
* Invoked to manually add attributions
*/
private void addAdditionalAttributions() {
if (withTelemetryAttribution) {
attributions.add(new Attribution(Attribution.TELEMETRY, Attribution.TELEMETRY_URL));
}
}
/**
* Convert a string to a spanned html representation.
*
* @param html the string to convert
* @return the spanned html representation
*/
private static Spanned fromHtml(String html) {
Spanned result;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
} else {
result = Html.fromHtml(html);
}
return result;
}
/**
* Builder to configure using an AttributionParser.
* <p>
* AttributionData, set with {@link #withAttributionData(String...)}, is the only required property to build
* the underlying AttributionParser. Other properties include trimming the copyright sign, adding telemetry
* attribution or hiding attribution as improve this map and Mapbox.
* </p>
*/
public static class Options {
private boolean withImproveMap = true;
private boolean withCopyrightSign = true;
private boolean withTelemetryAttribution = false;
private boolean withMapboxAttribution = true;
private String[] attributionDataStringArray;
public Options withAttributionData(String... attributionData) {
this.attributionDataStringArray = attributionData;
return this;
}
public Options withImproveMap(boolean withImproveMap) {
this.withImproveMap = withImproveMap;
return this;
}
public Options withCopyrightSign(boolean withCopyrightSign) {
this.withCopyrightSign = withCopyrightSign;
return this;
}
public Options withTelemetryAttribution(boolean withTelemetryAttribution) {
this.withTelemetryAttribution = withTelemetryAttribution;
return this;
}
public Options withMapboxAttribution(boolean withMapboxAttribution) {
this.withMapboxAttribution = withMapboxAttribution;
return this;
}
public AttributionParser build() {
if (attributionDataStringArray == null) {
throw new IllegalStateException("Using builder without providing attribution data");
}
String fullAttributionString = parseAttribution(attributionDataStringArray);
AttributionParser attributionParser = new AttributionParser(
fullAttributionString,
withImproveMap,
withCopyrightSign,
withTelemetryAttribution,
withMapboxAttribution
);
attributionParser.parse();
return attributionParser;
}
private String parseAttribution(String[] attribution) {
StringBuilder builder = new StringBuilder();
for (String attr : attribution) {
if (!attr.isEmpty()) {
builder.append(attr);
}
}
return builder.toString();
}
}
}

View File

@@ -22,6 +22,7 @@ import javax.net.ssl.SSLException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Dispatcher;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@@ -31,7 +32,8 @@ import timber.log.Timber;
class HTTPRequest implements Callback {
private static OkHttpClient mClient = new OkHttpClient();
private static OkHttpClient mClient = new OkHttpClient.Builder().dispatcher(getDispatcher()).build();
private static boolean logEnabled = true;
private String USER_AGENT_STRING = null;
private static final int CONNECTION_ERROR = 0;
@@ -47,6 +49,14 @@ class HTTPRequest implements Callback {
private Call mCall;
private Request mRequest;
private static Dispatcher getDispatcher() {
Dispatcher dispatcher = new Dispatcher();
// Matches core limit set on
// https://github.com/mapbox/mapbox-gl-native/blob/master/platform/android/src/http_file_source.cpp#L192
dispatcher.setMaxRequestsPerHost(20);
return dispatcher;
}
private native void nativeOnFailure(int type, String message);
private native void nativeOnResponse(int code, String etag, String modified, String cacheControl, String expires,
@@ -85,14 +95,7 @@ class HTTPRequest implements Callback {
}
mRequest = builder.build();
mCall = mClient.newCall(mRequest);
// TODO remove code block for workaround in #10303
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
mCall.enqueue(this);
} else {
// Calling execute instead of enqueue is a workaround for #10303
onResponse(mCall, mCall.execute());
}
mCall.enqueue(this);
} catch (Exception exception) {
onFailure(exception);
}
@@ -116,12 +119,15 @@ class HTTPRequest implements Callback {
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
Timber.v("[HTTP] Request was successful (code = %s).", response.code());
} else {
// We don't want to call this unsuccessful because a 304 isn't really an error
String message = !TextUtils.isEmpty(response.message()) ? response.message() : "No additional information";
Timber.d("[HTTP] Request with response code = %s: %s", response.code(), message);
if (logEnabled) {
if (response.isSuccessful()) {
Timber.v("[HTTP] Request was successful (code = %s).", response.code());
} else {
// We don't want to call this unsuccessful because a 304 isn't really an error
String message = !TextUtils.isEmpty(response.message()) ? response.message() : "No additional information";
Timber.d("[HTTP] Request with response code = %s: %s", response.code(), message);
}
}
byte[] body;
@@ -165,13 +171,15 @@ class HTTPRequest implements Callback {
String errorMessage = e.getMessage() != null ? e.getMessage() : "Error processing the request";
if (type == TEMPORARY_ERROR) {
Timber.d("Request failed due to a temporary error: %s", errorMessage);
} else if (type == CONNECTION_ERROR) {
Timber.i("Request failed due to a connection error: %s", errorMessage);
} else {
// PERMANENT_ERROR
Timber.w("Request failed due to a permanent error: %s", errorMessage);
if (logEnabled) {
if (type == TEMPORARY_ERROR) {
Timber.d("Request failed due to a temporary error: %s", errorMessage);
} else if (type == CONNECTION_ERROR) {
Timber.i("Request failed due to a connection error: %s", errorMessage);
} else {
// PERMANENT_ERROR
Timber.w("Request failed due to a permanent error: %s", errorMessage);
}
}
mLock.lock();
@@ -205,4 +213,8 @@ class HTTPRequest implements Callback {
return "";
}
}
static void enableLog(boolean enabled) {
logEnabled = enabled;
}
}

View File

@@ -0,0 +1,19 @@
package com.mapbox.mapboxsdk.http;
/**
* Utility class for setting HttpRequest configurations
*/
public class HttpRequestUtil {
/**
* Set the log state of HttpRequest.
* <p>
* This configuration will outlast the lifecycle of the Map.
* </p>
*
* @param enabled True will enable logging, false will disable
*/
public static void setLogEnabled(boolean enabled) {
HTTPRequest.enableLog(enabled);
}
}

View File

@@ -32,7 +32,7 @@ import com.mapzen.android.lost.api.LostApiClient;
* @deprecated Use a {@link Mapbox#getLocationEngine()} instead.
*/
@Deprecated
public class LocationSource extends LocationEngine implements LocationListener {
public class LocationSource extends LocationEngine implements LostApiClient.ConnectionCallbacks, LocationListener {
private Context context;
private LostApiClient lostApiClient;
@@ -45,7 +45,9 @@ public class LocationSource extends LocationEngine implements LocationListener {
public LocationSource(Context context) {
super();
this.context = context.getApplicationContext();
lostApiClient = new LostApiClient.Builder(this.context).build();
lostApiClient = new LostApiClient.Builder(this.context)
.addConnectionCallbacks(this)
.build();
}
/**
@@ -61,12 +63,7 @@ public class LocationSource extends LocationEngine implements LocationListener {
*/
@Override
public void activate() {
if (!lostApiClient.isConnected()) {
lostApiClient.connect();
}
for (LocationEngineListener listener : locationListeners) {
listener.onConnected();
}
connect();
}
/**
@@ -76,7 +73,7 @@ public class LocationSource extends LocationEngine implements LocationListener {
*/
@Override
public void deactivate() {
if (lostApiClient.isConnected()) {
if (lostApiClient != null && lostApiClient.isConnected()) {
lostApiClient.disconnect();
}
}
@@ -92,6 +89,24 @@ public class LocationSource extends LocationEngine implements LocationListener {
return lostApiClient.isConnected();
}
/**
* Invoked when the location provider has connected.
*/
@Override
public void onConnected() {
for (LocationEngineListener listener : locationListeners) {
listener.onConnected();
}
}
/**
* Invoked when the location provider connection has been suspended.
*/
@Override
public void onConnectionSuspended() {
// Empty
}
/**
* Returns the Last known location is the location provider is connected and location permissions are granted.
*
@@ -102,7 +117,7 @@ public class LocationSource extends LocationEngine implements LocationListener {
public Location getLastLocation() {
if (lostApiClient.isConnected()) {
//noinspection MissingPermission
return LocationServices.FusedLocationApi.getLastLocation();
return LocationServices.FusedLocationApi.getLastLocation(lostApiClient);
}
return null;
}
@@ -136,7 +151,7 @@ public class LocationSource extends LocationEngine implements LocationListener {
if (lostApiClient.isConnected()) {
//noinspection MissingPermission
LocationServices.FusedLocationApi.requestLocationUpdates(request, this);
LocationServices.FusedLocationApi.requestLocationUpdates(lostApiClient, request, this);
}
}
@@ -146,7 +161,7 @@ public class LocationSource extends LocationEngine implements LocationListener {
@Override
public void removeLocationUpdates() {
if (lostApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(this);
LocationServices.FusedLocationApi.removeLocationUpdates(lostApiClient, this);
}
}
@@ -171,4 +186,14 @@ public class LocationSource extends LocationEngine implements LocationListener {
listener.onLocationChanged(location);
}
}
private void connect() {
if (lostApiClient != null) {
if (lostApiClient.isConnected()) {
onConnected();
} else {
lostApiClient.connect();
}
}
}
}

View File

@@ -302,12 +302,14 @@ class AnnotationManager {
}
for (Marker marker : selectedMarkers) {
if (marker.isInfoWindowShown()) {
marker.hideInfoWindow();
}
if (marker != null) {
if (marker.isInfoWindowShown()) {
marker.hideInfoWindow();
}
if (marker instanceof MarkerView) {
markerViewManager.deselect((MarkerView) marker, false);
if (marker instanceof MarkerView) {
markerViewManager.deselect((MarkerView) marker, false);
}
}
}

View File

@@ -7,22 +7,20 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.attribution.Attribution;
import com.mapbox.mapboxsdk.attribution.AttributionParser;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.style.sources.Source;
import com.mapbox.services.android.telemetry.MapboxTelemetry;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
/**
* Responsible for managing attribution interactions on the map.
@@ -39,8 +37,8 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface.
private final Context context;
private final MapboxMap mapboxMap;
private String[] attributionKeys;
private HashMap<String, String> attributionMap;
private String[] attributionTitles;
private Set<Attribution> attributionSet;
AttributionDialogManager(@NonNull Context context, @NonNull MapboxMap mapboxMap) {
this.context = context;
@@ -50,18 +48,26 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface.
// Called when someone presses the attribution icon on the map
@Override
public void onClick(View view) {
attributionMap = new AttributionBuilder(context, mapboxMap).build();
attributionSet = new AttributionBuilder(mapboxMap).build();
showAttributionDialog();
}
private void showAttributionDialog() {
attributionKeys = attributionMap.keySet().toArray(new String[attributionMap.size()]);
attributionTitles = getAttributionTitles();
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.mapbox_attributionsDialogTitle);
builder.setAdapter(new ArrayAdapter<>(context, R.layout.mapbox_attribution_list_item, attributionKeys), this);
builder.setAdapter(new ArrayAdapter<>(context, R.layout.mapbox_attribution_list_item, attributionTitles), this);
builder.show();
}
private String[] getAttributionTitles() {
List<String> titles = new ArrayList<>();
for (Attribution attribution : attributionSet) {
titles.add(attribution.getTitle());
}
return titles.toArray(new String[titles.size()]);
}
// Called when someone selects an attribution or telemetry settings from the dialog
@Override
public void onClick(DialogInterface dialog, int which) {
@@ -73,7 +79,7 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface.
}
private boolean isLatestEntry(int attributionKeyIndex) {
return attributionKeyIndex == attributionKeys.length - 1;
return attributionKeyIndex == attributionTitles.length - 1;
}
private void showTelemetryDialog() {
@@ -105,7 +111,8 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface.
}
private void showMapFeedbackWebPage(int which) {
String url = attributionMap.get(attributionKeys[which]);
Attribution[] attributions = attributionSet.toArray(new Attribution[attributionSet.size()]);
String url = attributions[which].getUrl();
if (url.contains(MAP_FEEDBACK_URL)) {
url = buildMapFeedbackMapUrl(mapboxMap.getCameraPosition());
}
@@ -132,46 +139,24 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface.
private static class AttributionBuilder {
private final HashMap<String, String> map = new LinkedHashMap<>();
private final Context context;
private final MapboxMap mapboxMap;
AttributionBuilder(Context context, MapboxMap mapboxMap) {
this.context = context.getApplicationContext();
AttributionBuilder(MapboxMap mapboxMap) {
this.mapboxMap = mapboxMap;
}
private HashMap<String, String> build() {
private Set<Attribution> build() {
List<String> attributions = new ArrayList<>();
for (Source source : mapboxMap.getSources()) {
parseAttribution(source.getAttribution());
attributions.add(source.getAttribution());
}
addTelemetryEntryToAttributionMap();
return map;
}
private void parseAttribution(String attributionSource) {
if (!TextUtils.isEmpty(attributionSource)) {
SpannableStringBuilder htmlBuilder = (SpannableStringBuilder) Html.fromHtml(attributionSource);
URLSpan[] urlSpans = htmlBuilder.getSpans(0, htmlBuilder.length(), URLSpan.class);
for (URLSpan urlSpan : urlSpans) {
map.put(resolveAnchorValue(htmlBuilder, urlSpan), urlSpan.getURL());
}
}
}
private String resolveAnchorValue(SpannableStringBuilder htmlBuilder, URLSpan urlSpan) {
int start = htmlBuilder.getSpanStart(urlSpan);
int end = htmlBuilder.getSpanEnd(urlSpan);
int length = end - start;
char[] charKey = new char[length];
htmlBuilder.getChars(start, end, charKey, 0);
return String.valueOf(charKey);
}
private void addTelemetryEntryToAttributionMap() {
String telemetryKey = context.getString(R.string.mapbox_telemetrySettings);
String telemetryLink = context.getString(R.string.mapbox_telemetryLink);
map.put(telemetryKey, telemetryLink);
return new AttributionParser.Options()
.withCopyrightSign(true)
.withImproveMap(true)
.withTelemetryAttribution(true)
.withAttributionData(attributions.toArray(new String[attributions.size()]))
.build().getAttributions();
}
}
}

View File

@@ -1,5 +1,6 @@
package com.mapbox.mapboxsdk.maps;
import android.os.Handler;
import android.support.annotation.NonNull;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -9,10 +10,16 @@ import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveCanceledListener;
import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveListener;
import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener;
/**
* Class responsible for dispatching camera change events to registered listeners.
*/
class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, MapboxMap.OnCameraMoveListener,
MapboxMap.OnCameraMoveCanceledListener, OnCameraIdleListener {
private final Handler handler = new Handler();
private boolean idle = true;
private int moveStartedReason;
private final CopyOnWriteArrayList<OnCameraMoveStartedListener> onCameraMoveStarted = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<OnCameraMoveCanceledListener> onCameraMoveCanceled = new CopyOnWriteArrayList<>();
@@ -24,6 +31,74 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M
private OnCameraMoveListener onCameraMoveListener;
private OnCameraIdleListener onCameraIdleListener;
private final Runnable onCameraMoveStartedRunnable = new Runnable() {
@Override
public void run() {
// deprecated API
if (onCameraMoveStartedListener != null) {
onCameraMoveStartedListener.onCameraMoveStarted(moveStartedReason);
}
// new API
if (!onCameraMoveStarted.isEmpty()) {
for (OnCameraMoveStartedListener cameraMoveStartedListener : onCameraMoveStarted) {
cameraMoveStartedListener.onCameraMoveStarted(moveStartedReason);
}
}
}
};
private final Runnable onCameraMoveRunnable = new Runnable() {
@Override
public void run() {
// deprecated API
if (onCameraMoveListener != null && !idle) {
onCameraMoveListener.onCameraMove();
}
// new API
if (!onCameraMove.isEmpty() && !idle) {
for (OnCameraMoveListener cameraMoveListener : onCameraMove) {
cameraMoveListener.onCameraMove();
}
}
}
};
private final Runnable onCameraMoveCancelRunnable = new Runnable() {
@Override
public void run() {
// deprecated API
if (onCameraMoveCanceledListener != null && !idle) {
onCameraMoveCanceledListener.onCameraMoveCanceled();
}
// new API
if (!onCameraMoveCanceled.isEmpty() && !idle) {
for (OnCameraMoveCanceledListener cameraMoveCanceledListener : onCameraMoveCanceled) {
cameraMoveCanceledListener.onCameraMoveCanceled();
}
}
}
};
private final Runnable onCameraIdleRunnable = new Runnable() {
@Override
public void run() {
// deprecated API
if (onCameraIdleListener != null) {
onCameraIdleListener.onCameraIdle();
}
// new API
if (!onCameraIdle.isEmpty()) {
for (OnCameraIdleListener cameraIdleListener : onCameraIdle) {
cameraIdleListener.onCameraIdle();
}
}
}
};
@Deprecated
void setOnCameraMoveStartedListener(OnCameraMoveStartedListener onCameraMoveStartedListener) {
this.onCameraMoveStartedListener = onCameraMoveStartedListener;
@@ -45,16 +120,13 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M
}
@Override
public void onCameraMoveStarted(int reason) {
public void onCameraMoveStarted(final int reason) {
if (!idle) {
return;
}
idle = false;
// deprecated API
if (onCameraMoveStartedListener != null) {
onCameraMoveStartedListener.onCameraMoveStarted(reason);
}
moveStartedReason = reason;
handler.post(onCameraMoveStartedRunnable);
// new API
if (!onCameraMoveStarted.isEmpty()) {
@@ -66,7 +138,7 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M
@Override
public void onCameraMove() {
// deprecated API
handler.post(onCameraMoveRunnable);
if (onCameraMoveListener != null && !idle) {
onCameraMoveListener.onCameraMove();
}
@@ -81,7 +153,7 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M
@Override
public void onCameraMoveCanceled() {
// deprecated API
handler.post(onCameraMoveCancelRunnable);
if (onCameraMoveCanceledListener != null && !idle) {
onCameraMoveCanceledListener.onCameraMoveCanceled();
}
@@ -98,7 +170,7 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M
public void onCameraIdle() {
if (!idle) {
idle = true;
// deprecated API
handler.post(onCameraIdleRunnable);
if (onCameraIdleListener != null) {
onCameraIdleListener.onCameraIdle();
}

View File

@@ -11,6 +11,9 @@ import android.view.ViewGroup;
import com.mapbox.mapboxsdk.utils.MapFragmentUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Fragment wrapper around a map view.
* <p>
@@ -25,10 +28,11 @@ import com.mapbox.mapboxsdk.utils.MapFragmentUtils;
*
* @see #getMapAsync(OnMapReadyCallback)
*/
public final class MapFragment extends Fragment {
public final class MapFragment extends Fragment implements OnMapReadyCallback {
private final List<OnMapReadyCallback> mapReadyCallbackList = new ArrayList<>();
private MapboxMap mapboxMap;
private MapView map;
private OnMapReadyCallback onMapReadyCallback;
/**
* Creates a default MapFragment instance
@@ -63,7 +67,9 @@ public final class MapFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
Context context = inflater.getContext();
return map = new MapView(context, MapFragmentUtils.resolveArgs(context, getArguments()));
map = new MapView(context, MapFragmentUtils.resolveArgs(context, getArguments()));
map.setVisibility(View.INVISIBLE);
return map;
}
/**
@@ -76,9 +82,16 @@ public final class MapFragment extends Fragment {
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
map.onCreate(savedInstanceState);
if (onMapReadyCallback != null) {
map.getMapAsync(onMapReadyCallback);
map.getMapAsync(this);
}
@Override
public void onMapReady(MapboxMap mapboxMap) {
this.mapboxMap = mapboxMap;
for (OnMapReadyCallback onMapReadyCallback : mapReadyCallbackList) {
onMapReadyCallback.onMapReady(mapboxMap);
}
map.setVisibility(View.VISIBLE);
}
/**
@@ -144,6 +157,7 @@ public final class MapFragment extends Fragment {
public void onDestroyView() {
super.onDestroyView();
map.onDestroy();
mapReadyCallbackList.clear();
}
/**
@@ -152,10 +166,10 @@ public final class MapFragment extends Fragment {
* @param onMapReadyCallback The callback to be invoked.
*/
public void getMapAsync(@NonNull final OnMapReadyCallback onMapReadyCallback) {
if (map == null) {
this.onMapReadyCallback = onMapReadyCallback;
if (mapboxMap == null) {
mapReadyCallbackList.add(onMapReadyCallback);
} else {
map.getMapAsync(onMapReadyCallback);
onMapReadyCallback.onMapReady(mapboxMap);
}
}
}

View File

@@ -550,7 +550,7 @@ final class MapGestureDetector {
return super.onScale(detector);
}
wasZoomingIn = (Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2)) >= 0;
wasZoomingIn = (Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2)) > 0;
if (tiltGestureOccurred) {
return false;
}

View File

@@ -712,19 +712,21 @@ public final class MapboxMap {
* @param callback the callback to be invoked when an animation finishes or is canceled
*/
public final void moveCamera(final CameraUpdate update, final MapboxMap.CancelableCallback callback) {
new Handler().post(new Runnable() {
@Override
public void run() {
transform.moveCamera(MapboxMap.this, update, callback);
// MapChange.REGION_DID_CHANGE_ANIMATED is not called for `jumpTo`
// invalidate camera position to provide OnCameraChange event.
invalidateCameraPosition();
transform.moveCamera(MapboxMap.this, update, callback);
// MapChange.REGION_DID_CHANGE_ANIMATED is not called for `jumpTo`
// invalidate camera position to provide OnCameraChange event.
invalidateCameraPosition();
if (callback != null) {
callback.onFinish();
if (callback != null) {
new Handler().post(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onFinish();
}
}
}
});
});
}
}
/**
@@ -848,12 +850,7 @@ public final class MapboxMap {
if (durationMs <= 0) {
throw new IllegalArgumentException("Null duration passed into easeCamera");
}
new Handler().post(new Runnable() {
@Override
public void run() {
transform.easeCamera(MapboxMap.this, update, durationMs, easingInterpolator, callback, isDismissable);
}
});
transform.easeCamera(MapboxMap.this, update, durationMs, easingInterpolator, callback, isDismissable);
}
/**
@@ -923,12 +920,8 @@ public final class MapboxMap {
if (durationMs <= 0) {
throw new IllegalArgumentException("Null duration passed into animageCamera");
}
new Handler().post(new Runnable() {
@Override
public void run() {
transform.animateCamera(MapboxMap.this, update, durationMs, callback);
}
});
transform.animateCamera(MapboxMap.this, update, durationMs, callback);
}
/**

View File

@@ -725,9 +725,12 @@ public class MapboxMapOptions implements Parcelable {
}
/**
* Set the font-family for generating glyphs locally for ideographs in the ‘CJK Unified Ideographs’
* Set the font family for generating glyphs locally for ideographs in the ‘CJK Unified Ideographs’
* and ‘Hangul Syllables’ ranges.
*
* The font family argument is passed to {@link android.graphics.Typeface#create(String, int)}.
* Default system fonts are defined in '/system/etc/fonts.xml'
*
* @param fontFamily font family for local ideograph generation.
* @return This
*/

View File

@@ -11,6 +11,9 @@ import android.view.ViewGroup;
import com.mapbox.mapboxsdk.utils.MapFragmentUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Support Fragment wrapper around a map view.
* <p>
@@ -25,10 +28,11 @@ import com.mapbox.mapboxsdk.utils.MapFragmentUtils;
*
* @see #getMapAsync(OnMapReadyCallback)
*/
public class SupportMapFragment extends Fragment {
public class SupportMapFragment extends Fragment implements OnMapReadyCallback {
private final List<OnMapReadyCallback> mapReadyCallbackList = new ArrayList<>();
private MapboxMap mapboxMap;
private MapView map;
private OnMapReadyCallback onMapReadyCallback;
/**
* Creates a default MapFragment instance
@@ -63,7 +67,9 @@ public class SupportMapFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
Context context = inflater.getContext();
return map = new MapView(context, MapFragmentUtils.resolveArgs(context, getArguments()));
map = new MapView(context, MapFragmentUtils.resolveArgs(context, getArguments()));
map.setVisibility(View.INVISIBLE);
return map;
}
/**
@@ -76,9 +82,16 @@ public class SupportMapFragment extends Fragment {
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
map.onCreate(savedInstanceState);
if (onMapReadyCallback != null) {
map.getMapAsync(onMapReadyCallback);
map.getMapAsync(this);
}
@Override
public void onMapReady(MapboxMap mapboxMap) {
this.mapboxMap = mapboxMap;
for (OnMapReadyCallback onMapReadyCallback : mapReadyCallbackList) {
onMapReadyCallback.onMapReady(mapboxMap);
}
map.setVisibility(View.VISIBLE);
}
/**
@@ -144,6 +157,7 @@ public class SupportMapFragment extends Fragment {
public void onDestroyView() {
super.onDestroyView();
map.onDestroy();
mapReadyCallbackList.clear();
}
/**
@@ -152,10 +166,10 @@ public class SupportMapFragment extends Fragment {
* @param onMapReadyCallback The callback to be invoked.
*/
public void getMapAsync(@NonNull final OnMapReadyCallback onMapReadyCallback) {
if (map == null) {
this.onMapReadyCallback = onMapReadyCallback;
if (mapboxMap == null) {
mapReadyCallbackList.add(onMapReadyCallback);
} else {
map.getMapAsync(onMapReadyCallback);
onMapReadyCallback.onMapReady(mapboxMap);
}
}
}

View File

@@ -1,6 +1,7 @@
package com.mapbox.mapboxsdk.maps;
import android.graphics.PointF;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
@@ -82,8 +83,15 @@ final class Transform implements MapView.OnMapChangedListener {
if (change == REGION_DID_CHANGE_ANIMATED) {
updateCameraPosition(invalidateCameraPosition());
if (cameraCancelableCallback != null) {
cameraCancelableCallback.onFinish();
cameraCancelableCallback = null;
new Handler().post(new Runnable() {
@Override
public void run() {
if (cameraCancelableCallback != null) {
cameraCancelableCallback.onFinish();
cameraCancelableCallback = null;
}
}
});
}
cameraChangeDispatcher.onCameraIdle();
mapView.removeOnMapChangedListener(this);
@@ -175,8 +183,15 @@ final class Transform implements MapView.OnMapChangedListener {
// notify animateCamera and easeCamera about cancelling
if (cameraCancelableCallback != null) {
cameraChangeDispatcher.onCameraIdle();
cameraCancelableCallback.onCancel();
cameraCancelableCallback = null;
new Handler().post(new Runnable() {
@Override
public void run() {
if (cameraCancelableCallback != null) {
cameraCancelableCallback.onCancel();
cameraCancelableCallback = null;
}
}
});
}
// cancel ongoing transitions

View File

@@ -33,7 +33,6 @@ import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.constants.MyBearingTracking;
import com.mapbox.mapboxsdk.constants.MyLocationTracking;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.location.LocationSource;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.Projection;
import com.mapbox.services.android.telemetry.location.LocationEngine;
@@ -162,7 +161,7 @@ public class MyLocationView extends View {
}
@Deprecated
public void init(LocationSource locationSource) {
public void init(LocationEngine locationSource) {
this.locationEngine = locationSource;
}

View File

@@ -204,6 +204,7 @@ public class OfflineManager {
}
ConnectivityReceiver.instance(context).activate();
FileSource.getInstance(context).activate();
createOfflineRegion(fileSource, definition, metadata, new CreateOfflineRegionCallback() {
@Override
@@ -212,6 +213,7 @@ public class OfflineManager {
@Override
public void run() {
ConnectivityReceiver.instance(context).deactivate();
FileSource.getInstance(context).deactivate();
callback.onCreate(offlineRegion);
}
});
@@ -223,6 +225,7 @@ public class OfflineManager {
@Override
public void run() {
ConnectivityReceiver.instance(context).deactivate();
FileSource.getInstance(context).deactivate();
callback.onError(error);
}
});

View File

@@ -400,9 +400,6 @@ public class OfflineRegion {
* When the operation is complete or encounters an error, the given callback will be
* executed on the main thread.
* </p>
* <p>
* After you call this method, you may not call any additional methods on this object.
* </p>
*
* @param bytes the metadata in bytes
* @param callback the callback to be invoked

View File

@@ -28,7 +28,7 @@ public class MapSnapshot {
}
/**
* @return the bitmap
* @return the large
*/
public Bitmap getBitmap() {
return bitmap;

View File

@@ -5,19 +5,31 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v4.content.res.ResourcesCompat;
import android.text.Html;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.attribution.AttributionLayout;
import com.mapbox.mapboxsdk.attribution.AttributionMeasure;
import com.mapbox.mapboxsdk.attribution.AttributionParser;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.constants.Style;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.storage.FileSource;
import timber.log.Timber;
/**
* The map snapshotter creates a bitmap of the map, rendered
* The map snapshotter creates a large of the map, rendered
* off the UI thread. The snapshotter itself must be used on
* the UI thread (for access to the main looper)
*/
@@ -269,43 +281,126 @@ public class MapSnapshotter {
* @param mapSnapshot the map snapshot to draw the overlay on
*/
protected void addOverlay(MapSnapshot mapSnapshot) {
Bitmap original = mapSnapshot.getBitmap();
Canvas canvas = new Canvas(original);
addLogo(canvas, original);
Bitmap snapshot = mapSnapshot.getBitmap();
Canvas canvas = new Canvas(snapshot);
int margin = (int) context.getResources().getDisplayMetrics().density * LOGO_MARGIN_DP;
drawOverlay(mapSnapshot, snapshot, canvas, margin);
}
private void drawOverlay(MapSnapshot mapSnapshot, Bitmap snapshot, Canvas canvas, int margin) {
AttributionMeasure measure = getAttributionMeasure(mapSnapshot, snapshot, margin);
AttributionLayout layout = measure.measure();
drawLogo(mapSnapshot, canvas, margin, layout);
drawAttribution(mapSnapshot, canvas, measure, layout);
}
private AttributionMeasure getAttributionMeasure(MapSnapshot mapSnapshot, Bitmap snapshot, int margin) {
Logo logo = createScaledLogo(snapshot);
TextView longText = createTextView(mapSnapshot, false, logo.getScale());
TextView shortText = createTextView(mapSnapshot, true, logo.getScale());
return new AttributionMeasure.Builder()
.setSnapshot(snapshot)
.setLogo(logo.getLarge())
.setLogoSmall(logo.getSmall())
.setTextView(longText)
.setTextViewShort(shortText)
.setMarginPadding(margin)
.build();
}
private void drawLogo(MapSnapshot mapSnapshot, Canvas canvas, int margin, AttributionLayout layout) {
if (mapSnapshot.isShowLogo()) {
drawLogo(mapSnapshot.getBitmap(), canvas, margin, layout);
}
}
private void drawLogo(Bitmap snapshot, Canvas canvas, int margin, AttributionLayout placement) {
Bitmap selectedLogo = placement.getLogo();
if (selectedLogo != null) {
canvas.drawBitmap(selectedLogo, margin, snapshot.getHeight() - selectedLogo.getHeight() - margin, null);
}
}
private void drawAttribution(MapSnapshot mapSnapshot, Canvas canvas,
AttributionMeasure measure, AttributionLayout layout) {
// draw attribution
PointF anchorPoint = layout.getAnchorPoint();
if (anchorPoint != null) {
drawAttribution(canvas, measure, anchorPoint);
} else {
Bitmap snapshot = mapSnapshot.getBitmap();
Timber.e("Could not generate attribution for snapshot size: %s x %s."
+ " You are required to provide your own attribution for the used sources: %s",
snapshot.getWidth(), snapshot.getHeight(), mapSnapshot.getAttributions());
}
}
private void drawAttribution(Canvas canvas, AttributionMeasure measure, PointF anchorPoint) {
canvas.save();
canvas.translate(anchorPoint.x, anchorPoint.y);
measure.getTextView().draw(canvas);
canvas.restore();
}
private TextView createTextView(MapSnapshot mapSnapshot, boolean shortText, float scale) {
int textColor = ResourcesCompat.getColor(context.getResources(), R.color.mapbox_gray_dark, context.getTheme());
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
TextView textView = new TextView(context);
textView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
);
textView.setSingleLine(true);
textView.setTextSize(10 * scale);
textView.setTextColor(textColor);
textView.setBackgroundResource(R.drawable.mapbox_rounded_corner);
textView.setText(Html.fromHtml(createAttributionString(mapSnapshot, shortText)));
textView.measure(widthMeasureSpec, heightMeasureSpec);
textView.layout(0, 0, textView.getMeasuredWidth(), textView.getMeasuredHeight());
return textView;
}
/**
* Draw a logo on the canvas created from the map snapshot.
* Create the attribution string.
*
* @param canvas the canvas to draw the bitmap on
* @param original the map snapshot image
* @param mapSnapshot the map snapshot to create the attribution for
* @param shortText indicates if the short variant of the string should be parsed
* @return the parsed attribution string
*/
private void addLogo(Canvas canvas, Bitmap original) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float margin = displayMetrics.density * LOGO_MARGIN_DP;
Bitmap logo = createScaledLogo(original);
canvas.drawBitmap(logo, margin, original.getHeight() - logo.getHeight() - margin, null);
private String createAttributionString(MapSnapshot mapSnapshot, boolean shortText) {
AttributionParser attributionParser = new AttributionParser.Options()
.withAttributionData(mapSnapshot.getAttributions())
.withCopyrightSign(false)
.withImproveMap(false)
.build();
return attributionParser.createAttributionString(shortText);
}
/**
* Create a scaled logo for a map snapshot.
*
* @param snapshot the map snapshot where the logo should be placed on
* @return the scaled bitmap logo
* @return the scaled large logo
*/
private Bitmap createScaledLogo(Bitmap snapshot) {
private Logo createScaledLogo(@NonNull Bitmap snapshot) {
Bitmap logo = BitmapFactory.decodeResource(context.getResources(), R.drawable.mapbox_logo_icon, null);
float scale = calculateLogoScale(snapshot, logo);
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
return Bitmap.createBitmap(logo, 0, 0, logo.getWidth(), logo.getHeight(), matrix, true);
Bitmap helmet = BitmapFactory.decodeResource(context.getResources(), R.drawable.mapbox_logo_helmet, null);
Bitmap large = Bitmap.createBitmap(logo, 0, 0, logo.getWidth(), logo.getHeight(), matrix, true);
Bitmap small = Bitmap.createBitmap(helmet, 0, 0, helmet.getWidth(), helmet.getHeight(), matrix, true);
return new Logo(large, small, scale);
}
/**
* Calculates the scale of the logo, only allow downscaling.
*
* @param snapshot the bitmap of the map snapshot
* @param logo the bitmap of the mapbox logo
* @param snapshot the large of the map snapshot
* @param logo the large of the mapbox logo
* @return the scale value
*/
private float calculateLogoScale(Bitmap snapshot, Bitmap logo) {
@@ -315,7 +410,14 @@ public class MapSnapshotter {
float prefWidth = logo.getWidth() / widthRatio;
float prefHeight = logo.getHeight() / heightRatio;
float calculatedScale = Math.min(prefWidth / logo.getWidth(), prefHeight / logo.getHeight()) * 2;
return calculatedScale < 1 ? calculatedScale : 1.0f;
if (calculatedScale > 1) {
// don't allow over-scaling
calculatedScale = 1.0f;
} else if (calculatedScale < 0.60f) {
// don't scale to low either
calculatedScale = 0.60f;
}
return calculatedScale;
}
/**
@@ -324,14 +426,17 @@ public class MapSnapshotter {
*
* @param snapshot the generated snapshot
*/
protected void onSnapshotReady(MapSnapshot snapshot) {
if (callback != null) {
if (snapshot.isShowLogo()) {
addOverlay(snapshot);
protected void onSnapshotReady(final MapSnapshot snapshot) {
new Handler().post(new Runnable() {
@Override
public void run() {
if (callback != null) {
addOverlay(snapshot);
callback.onSnapshotReady(snapshot);
reset();
}
}
callback.onSnapshotReady(snapshot);
reset();
}
});
}
/**
@@ -364,4 +469,28 @@ public class MapSnapshotter {
@Override
protected native void finalize() throws Throwable;
private class Logo {
private Bitmap large;
private Bitmap small;
private float scale;
public Logo(Bitmap large, Bitmap small, float scale) {
this.large = large;
this.small = small;
this.scale = scale;
}
public Bitmap getLarge() {
return large;
}
public Bitmap getSmall() {
return small;
}
public float getScale() {
return scale;
}
}
}

View File

@@ -14,14 +14,14 @@ public class LocalGlyphRasterizer {
/***
* Uses Android-native drawing code to rasterize a single glyph
* to a square @{link Bitmap} which can be returned to portable
* to a square {@link Bitmap} which can be returned to portable
* code for transformation into a Signed Distance Field glyph.
*
* @param fontFamily Font family string to pass to Typeface.create
* @param bold If true, use Typeface.BOLD option
* @param glyphID 16-bit Unicode BMP codepoint to draw
*
* @return Return a @{link Bitmap} to be displayed in the requested tile.
* @return Return a {@link Bitmap} to be displayed in the requested tile.
*/
@WorkerThread
protected static Bitmap drawGlyphBitmap(String fontFamily, boolean bold, char glyphID) {

View File

@@ -1,7 +1,7 @@
package com.mapbox.mapboxsdk.utils;
/**
* Comparisons from std sdk, which aren't available in API level <= 15
* Comparisons from std sdk, which aren't available in API level 15 and below
*/
public class Compare {