From 186f3662c0ad891a9f37e0c5871cda0995aa0aaf Mon Sep 17 00:00:00 2001 From: tobrun Date: Mon, 13 Nov 2017 11:24:16 +0100 Subject: [android] - finalise integration and layout logic --- .../mapbox/mapboxsdk/attribution/Attribution.java | 15 ++ .../mapboxsdk/attribution/AttributionLayout.java | 62 ++++++ .../mapboxsdk/attribution/AttributionMeasure.java | 230 +++++++++++++++++++++ .../mapboxsdk/attribution/AttributionParser.java | 120 +++++++++-- .../attribution/AttributionPlacement.java | 56 ----- .../mapboxsdk/attribution/AttributionProvider.java | 49 ----- .../mapbox/mapboxsdk/snapshotter/MapSnapshot.java | 2 +- .../mapboxsdk/snapshotter/MapSnapshotter.java | 209 ++++++++++--------- .../src/main/res/values/colors.xml | 1 + .../attribution/AttributionParseTest.java | 65 +++++- .../maps/attribution/AttributionLayoutTest.java | 111 ---------- .../activity/snapshot/MapSnapshotterActivity.java | 2 +- .../snapshot/MapSnapshotterMarkerActivity.java | 2 +- 13 files changed, 577 insertions(+), 347 deletions(-) create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionLayout.java create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionMeasure.java delete mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionPlacement.java delete mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionProvider.java delete mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/attribution/AttributionLayoutTest.java diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/Attribution.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/Attribution.java index 5bfee26bc1..0877b3ab97 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/Attribution.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/Attribution.java @@ -2,6 +2,14 @@ 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; @@ -14,6 +22,13 @@ public class Attribution { return title; } + public String getTitleAbbreviated() { + if (title.equals(OPENSTREETMAP)) { + return OPENSTREETMAP_ABBR; + } + return title; + } + public String getUrl() { return url; } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionLayout.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionLayout.java new file mode 100644 index 0000000000..b08a8353be --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionLayout.java @@ -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 + + '}'; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionMeasure.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionMeasure.java new file mode 100644 index 0000000000..667060168b --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionMeasure.java @@ -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.textView, 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 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); + } + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionParser.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionParser.java index 99100419c7..90bb23429f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionParser.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionParser.java @@ -8,35 +8,61 @@ import android.text.style.URLSpan; import java.util.LinkedHashSet; import java.util.Set; +/** + * Responsible for parsing attribution data coming from Sources and MapSnapshot. + *

+ * Exposes multiple configuration options to manipulate data being parsed. + * Use the Options object to build these configurations. + *

+ */ public class AttributionParser { private final Set attributions = new LinkedHashSet<>(); + private final String attributionData; + private final boolean withImproveMap; + private final boolean withCopyrightSign; + private final boolean withTelemetryAttribution; + private final boolean withMapboxAttribution; - private String attributionDataString; - private boolean withImproveMap; - private boolean withCopyrightSign; - private boolean withTelemetryAttribution; - private boolean withMapboxAttribution; - - AttributionParser(String attributionDataString, boolean withImproveMap, boolean withCopyrightSign, + AttributionParser(String attributionData, boolean withImproveMap, boolean withCopyrightSign, boolean withTelemetryAttribution, boolean withMapboxAttribution) { - this.attributionDataString = attributionDataString; + this.attributionData = attributionData; this.withImproveMap = withImproveMap; this.withCopyrightSign = withCopyrightSign; this.withTelemetryAttribution = withTelemetryAttribution; this.withMapboxAttribution = withMapboxAttribution; } + /** + * Get parsed attributions. + * + * @return the attributions + */ public Set getAttributions() { return attributions; } - public String getAttributionString() { + /** + * 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(attribution.getTitle()); + stringBuilder.append(!shortenedOutput ? attribution.getTitle() : attribution.getTitleAbbreviated()); if (counter != attributions.size()) { stringBuilder.append(" / "); } @@ -44,39 +70,76 @@ public class AttributionParser { return stringBuilder.toString(); } - void parse() { + /** + * Main attribution for configuration + */ + protected void parse() { parseAttributions(); addAdditionalAttributions(); } + /** + * Parse attributions + */ private void parseAttributions() { - SpannableStringBuilder htmlBuilder = (SpannableStringBuilder) fromHtml(attributionDataString); + 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 (isUrlSpanValid(url)) { - String title = parseAnchorValue(htmlBuilder, urlSpan); - attributions.add(new Attribution(title, url)); + if (isUrlValid(url)) { + String anchor = parseAnchorValue(htmlBuilder, urlSpan); + attributions.add(new Attribution(anchor, url)); } } - private boolean isUrlSpanValid(String 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("https://www.mapbox.com/map-feedback/"); + 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("https://www.mapbox.com/about/maps/"); + 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); @@ -86,6 +149,12 @@ public class AttributionParser { 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()); @@ -93,11 +162,12 @@ public class AttributionParser { return anchor; } + /** + * Invoked to manually add attributions + */ private void addAdditionalAttributions() { if (withTelemetryAttribution) { - String telemetryKey = "Telemetry Settings"; - String telemetryLink = "https://www.mapbox.com/telemetry/"; - attributions.add(new Attribution(telemetryKey, telemetryLink)); + attributions.add(new Attribution(Attribution.TELEMETRY, Attribution.TELEMETRY_URL)); } } @@ -117,6 +187,14 @@ public class AttributionParser { return result; } + /** + * Builder to configure using an AttributionParser. + *

+ * 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. + *

+ */ public static class Options { private boolean withImproveMap = true; private boolean withCopyrightSign = true; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionPlacement.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionPlacement.java deleted file mode 100644 index 1eedbb4f46..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionPlacement.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.mapbox.mapboxsdk.attribution; - -import android.graphics.Bitmap; -import android.graphics.PointF; -import android.support.annotation.Nullable; - -public class AttributionPlacement { - - private Bitmap logo; - private PointF anchorPoint; - - public AttributionPlacement(@Nullable Bitmap logo, @Nullable PointF anchorPoint) { - this.logo = logo; - this.anchorPoint = anchorPoint; - } - - public Bitmap getLogo() { - return logo; - } - - public PointF getAnchorPoint() { - return anchorPoint; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - AttributionPlacement that = (AttributionPlacement) 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 "AttributionPlacement{" + - "logo=" + logo + - ", anchorPoint=" + anchorPoint + - '}'; - } -} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionProvider.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionProvider.java deleted file mode 100644 index 762bbd37a9..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/attribution/AttributionProvider.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.mapbox.mapboxsdk.attribution; - -import android.graphics.Bitmap; -import android.graphics.PointF; -import android.widget.TextView; - -public class AttributionProvider { - - private Bitmap logo; - private Bitmap logoSmall; - private Bitmap snapshot; - private TextView textView; - private float margin; - - public AttributionProvider(Bitmap snapshot, Bitmap logo, Bitmap logoSmall, TextView textView, float marginPadding) { - this.snapshot = snapshot; - this.logo = logo; - this.logoSmall = logoSmall; - this.textView = textView; - this.margin = marginPadding; - } - - public AttributionPlacement calculateAttributionPlacement() { - float logoContainerWidth = logo.getWidth() + (2 * margin); - - PointF anchor = new PointF(snapshot.getWidth() - textView.getMeasuredWidth() - margin, snapshot.getHeight() - margin - textView.getMeasuredHeight()); - float textViewContainerWidth = textView.getMeasuredWidth() + margin; - if (logoContainerWidth + textViewContainerWidth <= snapshot.getWidth()) { - // it fits! align to right - return new AttributionPlacement(logo, anchor); - } else { - // it doesn't fit let's try again with a smaller logo - float smallLogoContainerWidth = logoSmall.getWidth() + (2 * margin); - if (smallLogoContainerWidth + textViewContainerWidth <= snapshot.getWidth()) { - // it fits! align to right + use small logo - return new AttributionPlacement(logoSmall, anchor); - } else { - // it doesn't fit let try without a logo - if (textViewContainerWidth + margin <= snapshot.getWidth()) { - // it fits - return new AttributionPlacement(null, anchor); - } else { - // it doesn't fit - return new AttributionPlacement(null, null); - } - } - } - } -} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java index eb4f94c428..38c1491461 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java @@ -28,7 +28,7 @@ public class MapSnapshot { } /** - * @return the bitmap + * @return the large */ public Bitmap getBitmap() { return bitmap; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java index 4d85ea4f6a..1c59bb468e 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java @@ -10,22 +10,26 @@ 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.attribution.AttributionPlacement; -import com.mapbox.mapboxsdk.attribution.AttributionProvider; 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) */ @@ -274,113 +278,129 @@ public class MapSnapshotter { /** * Draw an overlay on the map snapshot. * - * @param snapshot the map snapshot to draw the overlay on + * @param mapSnapshot the map snapshot to draw the overlay on */ - protected void addOverlay(MapSnapshot snapshot) { - Bitmap original = snapshot.getBitmap(); - Canvas canvas = new Canvas(original); - if (snapshot.isShowLogo()) { -// Logo logo = addLogo(canvas, original); - addAttribution(canvas, snapshot); - } + protected void addOverlay(MapSnapshot mapSnapshot) { + Bitmap snapshot = mapSnapshot.getBitmap(); + Canvas canvas = new Canvas(snapshot); + int margin = (int) context.getResources().getDisplayMetrics().density * LOGO_MARGIN_DP; + drawOverlay(mapSnapshot, snapshot, canvas, margin); } - /** - * Draw a logo on the canvas created from the map snapshot. - * - * @param canvas the canvas to draw the bitmap on - * @param original the map snapshot image - */ - private Logo addLogo(Canvas canvas, Bitmap original) { - DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); - float margin = displayMetrics.density * LOGO_MARGIN_DP; - Logo logo = createScaledLogo(original); - logo.setLeft(margin); - logo.setTop(original.getHeight() - logo.bitmap.getHeight() - margin); - canvas.drawBitmap(logo.bitmap, logo.left, logo.top, null); - return logo; + 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); } - /** - * Draw source attribution on the canvas created from the map snapshot. - * - * @param canvas the canvas to draw the attribution on - * @param mapSnapshot the map snapshot - */ - private void addAttribution(Canvas canvas, MapSnapshot mapSnapshot) { + 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); + } + } - DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); - int margin = (int) displayMetrics.density * LOGO_MARGIN_DP; - Bitmap original = mapSnapshot.getBitmap(); + 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(8/** logo.scale*/); - textView.setText(Html.fromHtml(createAttribution(mapSnapshot))); - int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(original.getWidth(), View.MeasureSpec.AT_MOST); - int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); -// textView.setPadding(margin, 0, margin, margin); + 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()); - - - Bitmap snapshot = mapSnapshot.getBitmap(); - Bitmap logo = BitmapFactory.decodeResource(context.getResources(), R.drawable.mapbox_logo_icon, null); - Bitmap logoSmall = BitmapFactory.decodeResource(context.getResources(), R.drawable.mapbox_logo_helmet, null); - AttributionProvider attributionProvider = new AttributionProvider(snapshot, logo, logoSmall, textView, margin); - AttributionPlacement placement = attributionProvider.calculateAttributionPlacement(); - - // draw logo - Bitmap selectedLogo = placement.getLogo(); - if (selectedLogo != null) { - canvas.drawBitmap(selectedLogo, margin, snapshot.getHeight() - logo.getHeight() - margin, null); - } - - PointF anchorPoint = placement.getAnchorPoint(); - if (anchorPoint != null) { - canvas.save(); - canvas.translate(anchorPoint.x, anchorPoint.y); - textView.draw(canvas); - canvas.restore(); - } + return textView; } - private String createAttribution(MapSnapshot mapSnapshot) { + /** + * Create the attribution string. + * + * @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 String createAttributionString(MapSnapshot mapSnapshot, boolean shortText) { AttributionParser attributionParser = new AttributionParser.Options() .withAttributionData(mapSnapshot.getAttributions()) .withCopyrightSign(false) .withImproveMap(false) .build(); - return attributionParser.getAttributionString(); + 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 Logo 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); - float scale = 1.0f; + float scale = calculateLogoScale(snapshot, logo); Matrix matrix = new Matrix(); matrix.postScale(scale, scale); - return new Logo(Bitmap.createBitmap(logo, 0, 0, logo.getWidth(), logo.getHeight(), matrix, true), scale); + 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) { @@ -390,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; } /** @@ -444,42 +471,26 @@ public class MapSnapshotter { protected native void finalize() throws Throwable; private class Logo { - Bitmap bitmap; - float left; - float top; - float scale; + private Bitmap large; + private Bitmap small; + private float scale; - public Logo(Bitmap bitmap, float scale) { - this.bitmap = bitmap; + public Logo(Bitmap large, Bitmap small, float scale) { + this.large = large; + this.small = small; this.scale = scale; } - public Bitmap getBitmap() { - return bitmap; - } - - public void setBitmap(Bitmap bitmap) { - this.bitmap = bitmap; - } - - public float getLeft() { - return left; - } - - public void setLeft(float left) { - this.left = left; - } - - public float getTop() { - return top; + public Bitmap getLarge() { + return large; } - public void setTop(float top) { - this.top = top; + public Bitmap getSmall() { + return small; } - public int getWidthContainer() { - return (int) (left + bitmap.getWidth() + left); + public float getScale() { + return scale; } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml index b51c890e5c..19007f503f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml @@ -1,5 +1,6 @@ + #5F5F5F #7D7F80 #1E8CAB diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/attribution/AttributionParseTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/attribution/AttributionParseTest.java index 8ca8b49b65..f25cf1b7d8 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/attribution/AttributionParseTest.java +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/attribution/AttributionParseTest.java @@ -207,7 +207,11 @@ public class AttributionParseTest { .withImproveMap(false) .build(); - assertEquals("Attribution string should match", "© Mapbox / OpenStreetMap" ,attributionParser.getAttributionString()); + assertEquals( + "Attribution string should match", + "© Mapbox / OpenStreetMap", + attributionParser.createAttributionString() + ); } @@ -218,10 +222,13 @@ public class AttributionParseTest { .withImproveMap(false) .build(); - assertEquals("Attribution string should match", "© Mapbox / © OpenStreetMap" ,attributionParser.getAttributionString()); + assertEquals( + "Attribution string should match", + "© Mapbox / © OpenStreetMap", + attributionParser.createAttributionString() + ); } - @Test public void testOutputWithoutCopyRightWithoutMapboxString() throws Exception { AttributionParser attributionParser = new AttributionParser.Options() @@ -231,10 +238,13 @@ public class AttributionParseTest { .withMapboxAttribution(false) .build(); - assertEquals("Attribution string should match", "© OpenStreetMap" ,attributionParser.getAttributionString()); + assertEquals( + "Attribution string should match", + "© OpenStreetMap", + attributionParser.createAttributionString() + ); } - @Test public void testOutputWithCopyRightWithoutMapboxString() throws Exception { AttributionParser attributionParser = new AttributionParser.Options() @@ -243,18 +253,57 @@ public class AttributionParseTest { .withMapboxAttribution(false) .build(); - assertEquals("Attribution string should match", "© OpenStreetMap" ,attributionParser.getAttributionString()); + assertEquals( + "Attribution string should match", + "© OpenStreetMap", + attributionParser.createAttributionString() + ); } @Test public void testOutputSatelliteString() throws Exception { AttributionParser attributionParser = new AttributionParser.Options() - .withAttributionData(STREETS_ATTRIBUTION, SATELLITE_ATTRIBUTION, "blabla","") + .withAttributionData(STREETS_ATTRIBUTION, SATELLITE_ATTRIBUTION, "blabla", "") .withImproveMap(false) .withCopyrightSign(false) .withMapboxAttribution(false) .build(); - assertEquals("Attribution string should match", "© OpenStreetMap / DigitalGlobe" ,attributionParser.getAttributionString()); + assertEquals( + "Attribution string should match", + "© OpenStreetMap / DigitalGlobe", + attributionParser.createAttributionString() + ); + } + + @Test + public void testShortOpenStreetMapString() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(STREETS_ATTRIBUTION, SATELLITE_ATTRIBUTION, "blabla", "") + .withImproveMap(false) + .withCopyrightSign(false) + .withMapboxAttribution(false) + .build(); + + assertEquals( + "Attribution string should match", + "© OSM / DigitalGlobe", + attributionParser.createAttributionString(true) + ); + } + + @Test + public void testShortOpenStreetMapWithoutCopyrightString() throws Exception { + AttributionParser attributionParser = new AttributionParser.Options() + .withAttributionData(STREETS_ATTRIBUTION, SATELLITE_ATTRIBUTION, "blabla", "") + .withImproveMap(false) + .withCopyrightSign(false) + .build(); + + assertEquals( + "Attribution string should match", + "© Mapbox / OSM / DigitalGlobe", + attributionParser.createAttributionString(true) + ); } } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/attribution/AttributionLayoutTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/attribution/AttributionLayoutTest.java deleted file mode 100644 index 0e45951c24..0000000000 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/attribution/AttributionLayoutTest.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.mapbox.mapboxsdk.maps.attribution; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.PointF; -import android.support.test.rule.ActivityTestRule; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import com.mapbox.mapboxsdk.attribution.AttributionParser; -import com.mapbox.mapboxsdk.attribution.AttributionPlacement; -import com.mapbox.mapboxsdk.attribution.AttributionProvider; -import com.mapbox.mapboxsdk.testapp.activity.FeatureOverviewActivity; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -import static junit.framework.Assert.assertEquals; - -public class AttributionLayoutTest { - - @Rule - public ActivityTestRule rule = new ActivityTestRule<>(FeatureOverviewActivity.class); - - private static final String SATELLITE_ATTRIBUTION = "© Mapbox © OpenStreetMap Improve this map © DigitalGlobe\n"; - private static final Bitmap LOGO = Bitmap.createBitmap(128, 56, Bitmap.Config.ARGB_8888); - private static final Bitmap LOGO_SMALL = Bitmap.createBitmap(56, 56, Bitmap.Config.ARGB_8888); - private static final String ATTRIBUTION = new AttributionParser.Options() - .withAttributionData(SATELLITE_ATTRIBUTION) - .withImproveMap(false) - .withCopyrightSign(false) - .build().getAttributionString(); - - private Context context; - private TextView textView; - private float margin; - - @Before - public void setUp() throws Exception { - context = rule.getActivity(); - margin = context.getResources().getDisplayMetrics().density * 4; - - textView = new TextView(context); - textView.setLayoutParams(new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT) - ); - textView.setSingleLine(true); - textView.setTextSize(8); - textView.setText(ATTRIBUTION); - } - - @Test - public void testLogoLayout() throws Exception { - Bitmap snapshot = Bitmap.createBitmap(650, 600, Bitmap.Config.ARGB_8888); - int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(snapshot.getWidth(), View.MeasureSpec.AT_MOST); - int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - textView.measure(widthMeasureSpec, heightMeasureSpec); - - AttributionProvider attributionProvider = new AttributionProvider(snapshot, LOGO, LOGO_SMALL, textView, margin); - - PointF expectedPoint = new PointF(snapshot.getWidth() - textView.getMeasuredWidth() - margin, snapshot.getHeight() - textView.getMeasuredHeight() - margin); - AttributionPlacement expected = new AttributionPlacement(LOGO, expectedPoint); - AttributionPlacement actual = attributionProvider.calculateAttributionPlacement(); - assertEquals("Calculated placement should match: ", expected, actual); - } - - @Test - public void testSmallLogoLayout() { - Bitmap snapshot = Bitmap.createBitmap(512, 512, Bitmap.Config.ARGB_8888); - int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(snapshot.getWidth(), View.MeasureSpec.AT_MOST); - int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - textView.measure(widthMeasureSpec, heightMeasureSpec); - - AttributionProvider attributionProvider = new AttributionProvider(snapshot, LOGO, LOGO_SMALL, textView, margin); - - PointF expectedPoint = new PointF(snapshot.getWidth() - textView.getMeasuredWidth() - margin, snapshot.getHeight() - textView.getMeasuredHeight() - margin); - AttributionPlacement expected = new AttributionPlacement(LOGO_SMALL, expectedPoint); - AttributionPlacement actual = attributionProvider.calculateAttributionPlacement(); - assertEquals("Calculated placement should match: ", expected, actual); - } - - @Test - public void testNoLogoLayout() { - Bitmap snapshot = Bitmap.createBitmap(415, 415, Bitmap.Config.ARGB_8888); - int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - textView.measure(widthMeasureSpec, heightMeasureSpec); - - AttributionProvider attributionProvider = new AttributionProvider(snapshot, LOGO, LOGO_SMALL, textView, margin); - - PointF expectedPoint = new PointF(snapshot.getWidth() - textView.getMeasuredWidth() - margin, snapshot.getHeight() - textView.getMeasuredHeight() - margin); - AttributionPlacement expected = new AttributionPlacement(null, expectedPoint); - AttributionPlacement actual = attributionProvider.calculateAttributionPlacement(); - assertEquals("Calculated placement should match: ", expected, actual); - } - - @Test - public void testNoAttributionLayout() { - Bitmap snapshot = Bitmap.createBitmap(25, 25, Bitmap.Config.ARGB_8888); - int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(snapshot.getWidth(), View.MeasureSpec.AT_MOST); - int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - textView.measure(widthMeasureSpec, heightMeasureSpec); - - AttributionProvider attributionProvider = new AttributionProvider(snapshot, LOGO, LOGO_SMALL, textView, margin); - - AttributionPlacement expected = new AttributionPlacement(null, null); - AttributionPlacement actual = attributionProvider.calculateAttributionPlacement(); - assertEquals("Calculated placement should match: ", expected, actual); - } -} \ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java index 245786e1d0..c4fe93d200 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java @@ -69,7 +69,7 @@ public class MapSnapshotterActivity extends AppCompatActivity { .withPixelRatio(1) // Optionally the style - .withStyle((column + row) % 2 == 0 ? Style.TRAFFIC_DAY : Style.DARK); + .withStyle((column + row) % 2 == 0 ? Style.MAPBOX_STREETS : Style.DARK); // Optionally the visible region if (row % 2 == 0) { diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterMarkerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterMarkerActivity.java index 8416a5017a..b690f18b6a 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterMarkerActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterMarkerActivity.java @@ -44,7 +44,7 @@ public class MapSnapshotterMarkerActivity extends AppCompatActivity implements M getApplicationContext(), new MapSnapshotter .Options(Math.min(container.getMeasuredWidth(), 1024), Math.min(container.getMeasuredHeight(), 1024)) - .withStyle(Style.SATELLITE_STREETS) + .withStyle(Style.OUTDOORS) .withCameraPosition(new CameraPosition.Builder().target(new LatLng(52.090737, 5.121420)).zoom(15).build()) ); mapSnapshotter.start(MapSnapshotterMarkerActivity.this); -- cgit v1.2.1