diff --git a/OsmAnd-java/src/main/java/net/osmand/data/RotatedTileBox.java b/OsmAnd-java/src/main/java/net/osmand/data/RotatedTileBox.java
index 57c0080625..2219a0a6d6 100644
--- a/OsmAnd-java/src/main/java/net/osmand/data/RotatedTileBox.java
+++ b/OsmAnd-java/src/main/java/net/osmand/data/RotatedTileBox.java
@@ -2,6 +2,8 @@ package net.osmand.data;
import net.osmand.util.MapUtils;
+import java.util.Objects;
+
public class RotatedTileBox {
/// primary fields
@@ -69,6 +71,57 @@ public class RotatedTileBox {
}
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ RotatedTileBox tileBox = (RotatedTileBox) o;
+ return this.compare(tileBox.lat, lat) &&
+ this.compare(tileBox.lon, lon) &&
+ this.compare(tileBox.rotate, rotate) &&
+ this.compare(tileBox.density, density) &&
+ zoom == tileBox.zoom &&
+ this.compare(tileBox.mapDensity, mapDensity) &&
+ this.compare(tileBox.zoomAnimation, zoomAnimation) &&
+ this.compare(tileBox.zoomFloatPart, zoomFloatPart) &&
+ cx == tileBox.cx &&
+ cy == tileBox.cy &&
+ pixWidth == tileBox.pixWidth &&
+ pixHeight == tileBox.pixHeight &&
+ this.compare(tileBox.zoomFactor, zoomFactor) &&
+ this.compare(tileBox.rotateCos, rotateCos) &&
+ this.compare(tileBox.rotateSin, rotateSin) &&
+ this.compare(tileBox.oxTile, oxTile) &&
+ this.compare(tileBox.oyTile, oyTile);
+ }
+
+ private double E = 0.0001;
+
+ private boolean compare(float lon, float lon1) {
+ return Math.abs(lon1-lon) < E;
+ }
+
+ private boolean compare(double lon, double lon1) {
+ return Math.abs(lon1-lon) < E;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1 + (int)lat +
+ 3* (int)(lon*1/E) +
+ 5* (int)(rotate*1/E) +
+ 7* (int)(density*1/E) +
+ 11* zoom +
+ 13* (int)(mapDensity*1/E) +
+ 17* (int)(zoomAnimation*1/E) +
+ 19* (int)(zoomFloatPart*1/E) +
+ 23* cx +
+ 29* cy +
+ 31* pixWidth +
+ 37* pixHeight;
+ return result;
+ }
+
public void calculateDerivedFields() {
zoomFactor = Math.pow(2, zoomAnimation + zoomFloatPart) * 256 * mapDensity;
double rad = Math.toRadians(this.rotate);
diff --git a/OsmAnd/assets/server/css/leaflet.awesome-markers.css b/OsmAnd/assets/server/css/leaflet.awesome-markers.css
new file mode 100644
index 0000000000..266b57271e
--- /dev/null
+++ b/OsmAnd/assets/server/css/leaflet.awesome-markers.css
@@ -0,0 +1,57 @@
+/*
+Author: L. Voogdt
+License: MIT
+Version: 1.0
+*/
+
+/* Marker setup */
+.awesome-marker {
+ margin-top:-42px;
+ margin-left: -17px;
+ position:absolute;
+ left:0;
+ top:0;
+ display: block;
+ text-align: center;
+}
+
+.awesome-marker .material-icons {
+ font-size: 17px;
+}
+
+.awesome-marker-shadow {
+ background: url('/images/markers-shadow.png') no-repeat 0 0;
+ width: 36px;
+ height: 16px;
+}
+
+/* Retina displays */
+@media (min--moz-device-pixel-ratio: 1.5),(-o-min-device-pixel-ratio: 3/2),
+(-webkit-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio: 1.5),(min-resolution: 1.5dppx) {
+ .awesome-marker-shadow {
+ background-image: url('/images/markers-shadow@2x.png');
+ background-size: 35px 16px;
+ }
+}
+
+.awesome-marker i {
+ color: #333;
+ margin-top: 10px;
+ display: inline-block;
+ font-size: 14px;
+}
+
+.awesome-marker .icon-white {
+ color: #fff;
+}
+
+/* Colors */
+
+.awesome-marker-background {
+ fill: #fff;
+}
+
+.awesome-marker:hover {
+ top: 2px;
+ opacity: 0.9;
+}
diff --git a/OsmAnd/assets/server/go.html b/OsmAnd/assets/server/go.html
new file mode 100644
index 0000000000..8837fdbcd2
--- /dev/null
+++ b/OsmAnd/assets/server/go.html
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+ OsmAnd - Offline Mobile Maps and Navigation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OsmAnd/assets/server/images/markers-matte.png b/OsmAnd/assets/server/images/markers-matte.png
new file mode 100644
index 0000000000..178258665d
Binary files /dev/null and b/OsmAnd/assets/server/images/markers-matte.png differ
diff --git a/OsmAnd/assets/server/images/markers-matte@2x.png b/OsmAnd/assets/server/images/markers-matte@2x.png
new file mode 100644
index 0000000000..c981244dd3
Binary files /dev/null and b/OsmAnd/assets/server/images/markers-matte@2x.png differ
diff --git a/OsmAnd/assets/server/images/markers-plain.png b/OsmAnd/assets/server/images/markers-plain.png
new file mode 100644
index 0000000000..763f358932
Binary files /dev/null and b/OsmAnd/assets/server/images/markers-plain.png differ
diff --git a/OsmAnd/assets/server/images/markers-shadow.png b/OsmAnd/assets/server/images/markers-shadow.png
new file mode 100644
index 0000000000..33cf955047
Binary files /dev/null and b/OsmAnd/assets/server/images/markers-shadow.png differ
diff --git a/OsmAnd/assets/server/images/markers-shadow@2x.png b/OsmAnd/assets/server/images/markers-shadow@2x.png
new file mode 100644
index 0000000000..1116503f6a
Binary files /dev/null and b/OsmAnd/assets/server/images/markers-shadow@2x.png differ
diff --git a/OsmAnd/assets/server/images/markers-soft.png b/OsmAnd/assets/server/images/markers-soft.png
new file mode 100644
index 0000000000..9ee4c348dd
Binary files /dev/null and b/OsmAnd/assets/server/images/markers-soft.png differ
diff --git a/OsmAnd/assets/server/images/markers-soft@2x.png b/OsmAnd/assets/server/images/markers-soft@2x.png
new file mode 100644
index 0000000000..540ce63759
Binary files /dev/null and b/OsmAnd/assets/server/images/markers-soft@2x.png differ
diff --git a/OsmAnd/assets/server/scripts/go.js b/OsmAnd/assets/server/scripts/go.js
new file mode 100644
index 0000000000..6910c73046
--- /dev/null
+++ b/OsmAnd/assets/server/scripts/go.js
@@ -0,0 +1,273 @@
+var requestUtils={
+ 'getParamValue':function(paramName){
+ let value= (location.search.split(paramName + '=')[1]||'').split('&')[0];
+ if (value && value.length > 0){
+ return value;
+ }
+ return null;
+ },
+ 'isIOS':function(){
+ return /(iPad|iPhone|iPod)/g.test( navigator.userAgent );
+ },
+ 'redirect':function(newUrl){
+ document.location = newUrl;
+ }
+};
+
+var goMap = {
+ 'config':{
+ 'containerid': 'gocontainer',
+ 'defaults':{
+ 'lat':50.27,
+ 'lon':30.30,
+ 'zoom':13
+ }
+ },
+ 'utils':{
+ 'getPointFromUrl':function(){
+ let point = {};
+ point.lat = requestUtils.getParamValue('lat');
+ point.lon = requestUtils.getParamValue('lon');
+ point.zoom = requestUtils.getParamValue('z');
+ return point;
+ },
+ 'isPointComplete':function(point){
+ if (!point.lat || !point.lon){
+ return false;
+ }
+ return true;
+ },
+ 'extendPoint':function(initialPoint, newPoint){
+ let point={};
+ point.lat=newPoint.lat;
+ if (!point.lat || point.lat == null){
+ point.lat = initialPoint.lat;
+ }
+ point.lon=newPoint.lon;
+ if (!point.lon || point.lon == null){
+ point.lon = initialPoint.lon;
+ }
+ point.zoom=newPoint.zoom;
+ if (!point.zoom || point.zoom == null){
+ point.zoom = initialPoint.zoom;
+ }
+ return point;
+ }
+ },
+ 'init': function(config){
+ if (config && typeof (config) == 'object') {
+ $.extend(goMap.config, config);
+ }
+ goMap.$container = $('#' + goMap.config.containerid);
+ goMap.$footer = goMap.$container.find('.gofooter');
+ goMap.$latitude = goMap.$container.find('.latitude');
+ goMap.$longitude = goMap.$container.find('.longitude');
+
+ let inputPoint = goMap.utils.getPointFromUrl();
+ goMap.point = goMap.utils.extendPoint(goMap.config.defaults, inputPoint);
+ goMap.refreshCoordinates();
+
+ goMap.map =$.mapwidget();
+ goMap.map.showPoint(goMap.point);
+
+ let inputComplete = goMap.utils.isPointComplete(inputPoint);
+ if (inputComplete){
+ goMap.map.addMarker(goMap.point);
+ }
+ goMap.point = goMap.utils.getPointFromUrl();
+ },
+ 'refreshCoordinates':function(){
+ goMap.$latitude.text(goMap.point.lat);
+ goMap.$longitude.text(goMap.point.lon);
+ }
+};
+
+function toColor(num) {
+ num >>>= 0;
+ var b = num & 0xFF,
+ g = (num & 0xFF00) >>> 8,
+ r = (num & 0xFF0000) >>> 16,
+ a = ( (num & 0xFF000000) >>> 24 ) / 255 ;
+ return "rgba(" + [r, g, b, a].join(",") + ")";
+ }
+
+(function($) {
+ $.mapwidget = function(config) {
+ var loc = goMap.point.lat + '/' + goMap.point.lon;
+ var lparams = '?mlat='+goMap.point.lat + '&mlon=' + goMap.point.lon;
+ var mapobj={
+ config: $.extend({
+ 'mapid':'map',
+ 'maxzoom':20,
+ 'maxnativezoom':19,
+ 'sourceurl':'https://tile.osmand.net/hd/{z}/{x}/{y}.png',
+ 'attribution':'© OpenStreetMap contributors'
+ }, config),
+ init:function(){
+ mapobj.map = L.map(mapobj.config.mapid);
+ L.tileLayer(mapobj.config.sourceurl, {
+ attribution: mapobj.config.attribution,
+ maxZoom: mapobj.config.maxzoom,
+ maxNativeZoom: mapobj.config.maxnativezoom
+ }).addTo(mapobj.map);
+ },
+ showPoint:function(point){
+ mapobj.map.setView([point.lat, point.lon], point.zoom);
+ },
+ addMarker:function(point){
+ L.marker([point.lat, point.lon]).addTo(mapobj.map);
+ },
+ addPopupMarker:function(favorite,onClickEvent){
+ window.point = favorite;
+ var point = {};
+ point.lat = favorite.latitude;
+ point.lon = favorite.longitude;
+ var popup = L.popup().setContent(
+ "name: " + favorite.name + "
" +
+ "address: " + favorite.address + "
"
+ + "category: " + favorite.category);
+ var customMarker = L.AwesomeMarkers.icon({
+ icon: 'home',
+ markerColor: toColor(favorite.color),
+ iconColor: toColor(favorite.color)
+ });
+ L.marker([point.lat, point.lon],{icon: customMarker})
+ .bindPopup(popup)
+ .addTo(mapobj.map)
+ .on('click', function(e) {
+ onClickEvent(e);
+ });
+ }
+ };
+ mapobj.init();
+ return {
+ showPoint: mapobj.showPoint,
+ addMarker: mapobj.addMarker,
+ addPopupMarker: mapobj.addPopupMarker
+ };
+ };
+})(jQuery);
+
+(function($) {
+ $.timer=function(config){
+ var timerobj={
+ config: $.extend({
+ 'timeoutInMs':300,
+ 'maxActionDelayInMs':2000,
+ 'action':function(){},
+ 'actionparams':null
+ }, config),
+ init:function(){
+ timerobj.timer = null;
+ timerobj.startDate = null;
+ },
+ start:function(){
+ timerobj.cancel();
+ timerobj.startDate = new Date();
+ timerobj.timer=setTimeout(timerobj.onTimer, timerobj.config.timeoutInMs);
+ },
+ cancel:function(){
+ if (timerobj.timer != null){
+ clearTimeout(timerobj.timer);
+ timerobj.timer = null;
+ timerobj.startDate = null;
+ }
+ },
+ onTimer:function(){
+ timerobj.timer= null;
+ let now = new Date();
+ if(now - timerobj.startDate < timerobj.config.maxActionDelayInMs){
+ timerobj.config.action(timerobj.config.actionparams);
+ }
+ }
+ };
+ timerobj.init();
+ return {
+ start:timerobj.start,
+ cancel:timerobj.cancel
+ };
+ };
+})(jQuery);
+
+var iosAppRedirect = {
+ config:{
+ appPrefix:'osmandmaps://',
+ containerid:'gocontainer',
+ cookieName:'OsmAndInstalled',
+ cookieNoExpirationTimeoutInDays:30
+ },
+ init:function(config){
+ if (config && typeof (config) == 'object') {
+ $.extend(iosAppRedirect.config, config);
+ }
+
+ if (!requestUtils.isIOS()){
+ return;
+ }
+ iosAppRedirect.$container = $('#' + iosAppRedirect.config.containerid);
+ iosAppRedirect.$overlay = iosAppRedirect.$container.find('.overlay');
+ iosAppRedirect.$popup = iosAppRedirect.$container.find('.popup');
+ iosAppRedirect.$yesBtn = iosAppRedirect.$container.find('.yes');
+ iosAppRedirect.$noBtn = iosAppRedirect.$container.find('.no');
+ iosAppRedirect.$cancelBtn = iosAppRedirect.$container.find('.cancel');
+ iosAppRedirect.applestorelink = iosAppRedirect.$container.find('.gobadges .apple a').attr('href');
+ iosAppRedirect.applink = iosAppRedirect.config.appPrefix + document.location.search;
+
+
+ if (iosAppRedirect.isAppInstalled() === "yes"){
+ iosAppRedirect.redirectToApp();
+ return;
+ }
+ if (iosAppRedirect.isAppInstalled() === "no"){
+ return;
+ }
+
+ iosAppRedirect.$yesBtn.on('click', function(){
+ iosAppRedirect.redirectToApp();
+ iosAppRedirect.closePopup();
+ });
+
+ iosAppRedirect.$noBtn.on('click', function(){
+ iosAppRedirect.setCookie(true);
+ iosAppRedirect.closePopup();
+ window.open(iosAppRedirect.applestorelink , '_blank');
+ });
+
+ iosAppRedirect.$cancelBtn.on('click', function(){
+ iosAppRedirect.setCookie(false);
+ iosAppRedirect.closePopup();
+ });
+ iosAppRedirect.openPopup();
+ },
+ isAppInstalled:function(){
+ return Cookies.get('OsmAndInstalled');
+ },
+ redirectToApp:function(){
+ iosAppRedirect.timer = $.timer({action:iosAppRedirect.clearCookie});
+ iosAppRedirect.timer.start();
+ requestUtils.redirect(iosAppRedirect.applink);
+ },
+ setCookie:function(appInstalled){
+ if (appInstalled === true){
+ Cookies.set(iosAppRedirect.config.cookieName, "yes");
+ }else{
+ Cookies.set(iosAppRedirect.config.cookieName, "no", { expires: iosAppRedirect.config.cookieNoExpirationTimeoutInDays });
+ }
+ },
+ clearCookie:function(){
+ Cookies.remove('OsmAndInstalled');
+ },
+ openPopup:function(){
+ iosAppRedirect.$overlay.show();
+ iosAppRedirect.$popup.show();
+ },
+ closePopup:function(){
+ iosAppRedirect.$overlay.hide();
+ iosAppRedirect.$popup.hide();
+ }
+};
+
+ $( document ).ready(function() {
+ goMap.init();
+ iosAppRedirect.init();
+ });
diff --git a/OsmAnd/assets/server/scripts/leaflet.awesome-markers.js b/OsmAnd/assets/server/scripts/leaflet.awesome-markers.js
new file mode 100644
index 0000000000..445fdc6336
--- /dev/null
+++ b/OsmAnd/assets/server/scripts/leaflet.awesome-markers.js
@@ -0,0 +1,134 @@
+/*
+ Leaflet.AwesomeMarkers, a plugin that adds colorful iconic markers for Leaflet, based on the Font Awesome icons
+ (c) 2012-2013, Lennard Voogdt
+
+ http://leafletjs.com
+ https://github.com/lvoogdt
+*/
+
+/*global L*/
+
+(function (window, document, undefined) {
+ "use strict";
+ /*
+ * Leaflet.AwesomeMarkers assumes that you have already included the Leaflet library.
+ */
+
+ L.AwesomeMarkers = {};
+
+ L.AwesomeMarkers.version = '2.0.1';
+
+ L.AwesomeMarkers.Icon = L.Icon.extend({
+ options: {
+ shadowAnchor: [10, 12],
+ shadowSize: [36, 16],
+ className: 'awesome-marker',
+ icon: 'block',
+ markerColor: 'white',
+ iconColor: 'white'
+ },
+
+ initialize: function (options) {
+ options = L.Util.setOptions(this, options);
+ },
+
+ createIcon: function () {
+ var options = L.Util.setOptions(this);
+ var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ var path = document.createElementNS('http://www.w3.org/2000/svg', "path");
+ var backgroundCircle = document.createElementNS('http://www.w3.org/2000/svg', "circle");
+ var icongroup = document.createElementNS('http://www.w3.org/2000/svg', "g");
+ var icon = document.createElementNS('http://www.w3.org/2000/svg', "text");
+
+ svg.setAttribute('width', '31');
+ svg.setAttribute('height', '42');
+ svg.setAttribute('class', 'awesome-marker');
+ svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
+
+ backgroundCircle.setAttribute('cx', '15.5');
+ backgroundCircle.setAttribute('cy', '15');
+ backgroundCircle.setAttribute('r', '11');
+ backgroundCircle.setAttribute('fill', options.markerColor);
+
+ path.setAttributeNS(null, "d", "M15.6,1c-7.7,0-14,6.3-14,14c0,10.5,14,26,14,26s14-15.5,14-26C29.6,7.3,23.3,1,15.6,1z");
+ //path.setAttribute('class', 'awesome-marker-background');
+ path.setAttribute('stroke', 'white');
+ path.setAttribute('style', 'fill:' + options.markerColor)
+
+ icon.textContent = options.icon;
+ icon.setAttribute('x', '7');
+ icon.setAttribute('y', '23');
+ icon.setAttribute('class', 'material-icons');
+ icon.setAttribute('fill', options.iconColor);
+ icon.setAttribute('font-family', 'Material Icons');
+
+ svg.appendChild(path);
+ svg.appendChild(backgroundCircle);
+ icongroup.appendChild(icon);
+ svg.appendChild(icongroup);
+
+ return svg;
+ },
+
+ _createInner: function() {
+ var iconClass, iconSpinClass = "", iconColorClass = "", iconColorStyle = "", options = this.options;
+
+ if (options.spin && typeof options.spinClass === "string") {
+ iconSpinClass = options.spinClass;
+ }
+
+ if (options.iconColor) {
+ if (options.iconColor === 'white' || options.iconColor === 'black') {
+ iconColorClass = "icon-" + options.iconColor;
+ } else {
+ iconColorStyle = "style='color: " + options.iconColor + "' ";
+ }
+ }
+ //return ""
+ return options.extraClasses + " " + iconClass + " " + iconSpinClass + " " + iconColorClass;
+ },
+
+ _setIconStyles: function (img, name) {
+ var options = this.options,
+ size = L.point(options[name === 'shadow' ? 'shadowSize' : 'iconSize']),
+ anchor;
+
+ if (name === 'shadow') {
+ anchor = L.point(options.shadowAnchor || options.iconAnchor);
+ } else {
+ anchor = L.point(options.iconAnchor);
+ }
+
+ if (!anchor && size) {
+ anchor = size.divideBy(2, true);
+ }
+
+ img.className = 'awesome-marker-' + name + ' ' + options.className;
+
+ if (anchor) {
+ img.style.marginLeft = (-anchor.x) + 'px';
+ img.style.marginTop = (-anchor.y) + 'px';
+ }
+
+ if (size) {
+ img.style.width = size.x + 'px';
+ img.style.height = size.y + 'px';
+ }
+ },
+
+ createShadow: function () {
+ var div = document.createElement('div');
+
+ this._setIconStyles(div, 'shadow');
+ return div;
+ }
+ });
+
+ L.AwesomeMarkers.icon = function (options) {
+ return new L.AwesomeMarkers.Icon(options);
+ };
+
+}(this, document));
+
+
+
diff --git a/OsmAnd/assets/server/scripts/leaflet.awesome-markers.min.js b/OsmAnd/assets/server/scripts/leaflet.awesome-markers.min.js
new file mode 100644
index 0000000000..376e57e68d
--- /dev/null
+++ b/OsmAnd/assets/server/scripts/leaflet.awesome-markers.min.js
@@ -0,0 +1,7 @@
+/*
+ Leaflet.AwesomeMarkers, a plugin that adds colorful iconic markers for Leaflet, based on the Font Awesome icons
+ (c) 2012-2013, Lennard Voogdt
+
+ http://leafletjs.com
+ https://github.com/lvoogdt
+*//*global L*/(function(e,t,n){"use strict";L.AwesomeMarkers={};L.AwesomeMarkers.version="2.0.1";L.AwesomeMarkers.Icon=L.Icon.extend({options:{iconSize:[35,45],iconAnchor:[17,42],popupAnchor:[1,-32],shadowAnchor:[10,12],shadowSize:[36,16],className:"awesome-marker",prefix:"glyphicon",spinClass:"fa-spin",icon:"home",markerColor:"blue",iconColor:"white"},initialize:function(e){e=L.Util.setOptions(this,e)},createIcon:function(){var e=t.createElement("div"),n=this.options;n.icon&&(e.innerHTML=this._createInner());n.bgPos&&(e.style.backgroundPosition=-n.bgPos.x+"px "+ -n.bgPos.y+"px");this._setIconStyles(e,"icon-"+n.markerColor);return e},_createInner:function(){var e,t="",n="",r="",i=this.options;i.icon.slice(0,i.prefix.length+1)===i.prefix+"-"?e=i.icon:e=i.prefix+"-"+i.icon;i.spin&&typeof i.spinClass=="string"&&(t=i.spinClass);i.iconColor&&(i.iconColor==="white"||i.iconColor==="black"?n="icon-"+i.iconColor:r="style='color: "+i.iconColor+"' ");return""},_setIconStyles:function(e,t){var n=this.options,r=L.point(n[t==="shadow"?"shadowSize":"iconSize"]),i;t==="shadow"?i=L.point(n.shadowAnchor||n.iconAnchor):i=L.point(n.iconAnchor);!i&&r&&(i=r.divideBy(2,!0));e.className="awesome-marker-"+t+" "+n.className;if(i){e.style.marginLeft=-i.x+"px";e.style.marginTop=-i.y+"px"}if(r){e.style.width=r.x+"px";e.style.height=r.y+"px"}},createShadow:function(){var e=t.createElement("div");this._setIconStyles(e,"shadow");return e}});L.AwesomeMarkers.icon=function(e){return new L.AwesomeMarkers.Icon(e)}})(this,document);
diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java
index f71a6c0fb6..f58acb8591 100644
--- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java
+++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java
@@ -136,6 +136,7 @@ import net.osmand.plus.routing.TransportRoutingHelper.TransportRouteCalculationP
import net.osmand.plus.search.QuickSearchDialogFragment;
import net.osmand.plus.search.QuickSearchDialogFragment.QuickSearchTab;
import net.osmand.plus.search.QuickSearchDialogFragment.QuickSearchType;
+import net.osmand.plus.server.ApiRouter;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.settings.backend.OsmAndAppCustomization.OsmAndAppCustomizationListener;
import net.osmand.plus.settings.backend.OsmandSettings;
@@ -309,6 +310,7 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
int h = dm.heightPixels - statusBarHeight;
mapView = new OsmandMapTileView(this, w, h);
+ ApiRouter.mapActivity = this;
if (app.getAppInitializer().checkAppVersionChanged() && WhatsNewDialogFragment.SHOW) {
SecondSplashScreenFragment.SHOW = false;
WhatsNewDialogFragment.SHOW = false;
diff --git a/OsmAnd/src/net/osmand/plus/activities/ServerActivity.java b/OsmAnd/src/net/osmand/plus/activities/ServerActivity.java
new file mode 100644
index 0000000000..eda412c599
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/activities/ServerActivity.java
@@ -0,0 +1,106 @@
+package net.osmand.plus.activities;
+
+import android.net.TrafficStats;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.StrictMode;
+import android.text.format.Formatter;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import net.osmand.plus.OsmandApplication;
+import net.osmand.plus.R;
+import net.osmand.plus.server.OsmAndHttpServer;
+
+import java.io.IOException;
+
+public class ServerActivity extends AppCompatActivity {
+ private boolean initialized = false;
+ private OsmAndHttpServer server;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ enableStrictMode();
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.server_activity);
+ findViewById(R.id.Button01).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (!initialized) {
+ updateTextView("Click second button to deactivate server");
+ initServer();
+ }
+ }
+ });
+ findViewById(R.id.Button03).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (initialized) {
+ updateTextView("Click first button to activate server");
+ deInitServer();
+ }
+ }
+ });
+ }
+
+ public static void enableStrictMode() {
+ StrictMode.setThreadPolicy(
+ new StrictMode.ThreadPolicy.Builder()
+ .detectDiskReads()
+ .detectDiskWrites()
+ .detectNetwork()
+ .penaltyLog()
+ .build());
+ StrictMode.setVmPolicy(
+ new StrictMode.VmPolicy.Builder()
+ .detectLeakedSqlLiteObjects()
+ .penaltyLog()
+ .build());
+ }
+
+
+ private void updateTextView(String text) {
+ ((TextView) findViewById(R.id.TextView02)).setText(text);
+ }
+
+ private void initServer() {
+ final int THREAD_ID = 10000;
+ TrafficStats.setThreadStatsTag(THREAD_ID);
+ OsmAndHttpServer.HOSTNAME = getDeviceAddress();
+ try {
+ server = new OsmAndHttpServer();
+ server.setAndroidApplication((OsmandApplication)this.getApplication());
+ initialized = true;
+ updateTextView("Server started at: http://" + getDeviceAddress() + ":" + OsmAndHttpServer.PORT);
+ } catch (IOException e) {
+ Toast.makeText(this,
+ e.getLocalizedMessage(),
+ Toast.LENGTH_SHORT).show();
+ e.printStackTrace();
+ }
+ }
+
+ private String getDeviceAddress() {
+ WifiManager wm = (WifiManager) this.getApplicationContext().getSystemService(WIFI_SERVICE);
+ String ip = Formatter.formatIpAddress(wm.getConnectionInfo().getIpAddress());
+ return ip != null ? ip : "0.0.0.0";
+ }
+
+ private void deInitServer() {
+ if (server != null){
+ server.closeAllConnections();
+ server.stop();
+ }
+ initialized = false;
+ }
+
+ @Override
+ protected void onDestroy() {
+ deInitServer();
+ super.onDestroy();
+ }
+}
diff --git a/OsmAnd/src/net/osmand/plus/server/ApiRouter.java b/OsmAnd/src/net/osmand/plus/server/ApiRouter.java
new file mode 100644
index 0000000000..a8e5ddc86d
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/server/ApiRouter.java
@@ -0,0 +1,266 @@
+package net.osmand.plus.server;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+import android.util.Pair;
+import android.webkit.MimeTypeMap;
+import com.google.gson.Gson;
+import fi.iki.elonen.NanoHTTPD;
+import net.osmand.data.FavouritePoint;
+import net.osmand.data.RotatedTileBox;
+import net.osmand.plus.OsmandApplication;
+import net.osmand.plus.activities.MapActivity;
+import net.osmand.plus.views.OsmandMapLayer;
+import net.osmand.plus.views.OsmandMapTileView;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.concurrent.*;
+
+import static fi.iki.elonen.NanoHTTPD.newFixedLengthResponse;
+
+public class ApiRouter implements OsmandMapTileView.IMapImageDrawListener {
+ private OsmandApplication androidContext;
+
+ public OsmandApplication getAndroidContext() {
+ return androidContext;
+ }
+
+ private final String FOLDER_NAME = "server";
+ private Gson gson = new Gson();
+ private Map endpoints = new HashMap<>();
+ //change to weakreference
+ public static MapActivity mapActivity;
+
+ public ApiRouter() {
+ initRoutes();
+ }
+
+ private void initRoutes() {
+ ApiEndpoint favorites = new ApiEndpoint();
+ favorites.uri = "/favorites";
+ favorites.apiCall = new ApiEndpoint.ApiCall() {
+ @Override
+ public NanoHTTPD.Response call(NanoHTTPD.IHTTPSession session) {
+ return newFixedLengthResponse(getFavoritesJson());
+ }
+ };
+ endpoints.put(favorites.uri, favorites);
+
+ final ApiEndpoint tile = new ApiEndpoint();
+ tile.uri = "/tile";
+ tile.apiCall = new ApiEndpoint.ApiCall() {
+ @Override
+ public NanoHTTPD.Response call(NanoHTTPD.IHTTPSession session) {
+ try {
+ return tileApiCall(session);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return ErrorResponses.response500;
+ }
+ };
+ endpoints.put(tile.uri, tile);
+ }
+
+ ExecutorService executor = Executors.newFixedThreadPool(3);
+
+ Map hashMap = new HashMap<>();
+ Map map = Collections.synchronizedMap(hashMap);
+
+ private synchronized NanoHTTPD.Response tileApiCall(NanoHTTPD.IHTTPSession session) {
+ int zoom = 0;
+ double lat = 0;//50.901430;
+ double lon = 0;//34.801775;
+ try {
+ String fullUri = session.getUri().replace("/tile/", "");
+ Scanner s = new Scanner(fullUri).useDelimiter("/");
+ zoom = s.nextInt();
+ lat = s.nextDouble();
+ lon = s.nextDouble();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return ErrorResponses.response500;
+ }
+ mapActivity.getMapView().setMapImageDrawListener(this);
+ Future> future;
+ final RotatedTileBox rotatedTileBox = new RotatedTileBox.RotatedTileBoxBuilder()
+ .setLocation(lat, lon)
+ .setZoom(zoom)
+ .setPixelDimensions(512, 512, 0.5f, 0.5f).build();
+ future = executor.submit(new Callable>() {
+ @Override
+ public Pair call() throws Exception {
+ Bitmap bmp;
+ while ((bmp = map.get(rotatedTileBox)) == null) {
+ Thread.sleep(1000);
+ }
+ return Pair.create(rotatedTileBox, bmp);
+ }
+ });
+ mapActivity.getMapView().setCurrentRotatedTileBox(rotatedTileBox);
+ try {
+ Pair pair = future.get();
+ Bitmap bitmap = pair.second;// mapActivity.getMapView().currentCanvas;
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
+ byte[] byteArray = stream.toByteArray();
+ ByteArrayInputStream str = new ByteArrayInputStream(byteArray);
+ return newFixedLengthResponse(
+ NanoHTTPD.Response.Status.OK,
+ "image/png",
+ str,
+ str.available());
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ return ErrorResponses.response500;
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ return ErrorResponses.response500;
+ }
+ }
+
+ public void setAndroidContext(OsmandApplication androidContext) {
+ this.androidContext = androidContext;
+ }
+
+ public NanoHTTPD.Response route(NanoHTTPD.IHTTPSession session) {
+ Log.d("SERVER", "URI: " + session.getUri());
+ String uri = session.getUri();
+ if (uri.equals("/")) return getStatic("/go.html");
+ if (uri.contains("/scripts/") ||
+ uri.contains("/images/") ||
+ uri.contains("/css/") ||
+ uri.contains("/fonts/") ||
+ uri.contains("/favicon.ico")
+ ) return getStatic(uri);
+ if (isApiUrl(uri)) {
+ return routeApi(session);
+ } else {
+ return routeContent(session);
+ }
+ }
+
+ private NanoHTTPD.Response routeApi(NanoHTTPD.IHTTPSession session) {
+ String uri = session.getUri();
+ //TODO rewrite
+ if (uri.contains("tile")) {
+ return endpoints.get("/tile").apiCall.call(session);
+ }
+ ApiEndpoint endpoint = endpoints.get(uri);
+ if (endpoint != null) {
+ return endpoint.apiCall.call(session);
+ }
+ return ErrorResponses.response404;
+ }
+
+ private boolean isApiUrl(String uri) {
+ for (String endpoint : endpoints.keySet()) {
+ //TODO rewrite contains
+ if (endpoint.equals(uri) || uri.contains("tile")) return true;
+ }
+ return false;
+ }
+
+ private NanoHTTPD.Response routeContent(NanoHTTPD.IHTTPSession session) {
+ String url = session.getUri();
+ //add index page
+ String responseText = getHtmlPage(url);
+ if (responseText != null) {
+ return newFixedLengthResponse(responseText);
+ } else {
+ return ErrorResponses.response404;
+ }
+ }
+
+ public NanoHTTPD.Response getStatic(String uri) {
+ InputStream is = null;
+ String mimeType = parseMimeType(uri);
+ if (androidContext != null) {
+ try {
+ is = androidContext.getAssets().open(FOLDER_NAME + uri);
+ if (is.available() == 0) {
+ return ErrorResponses.response404;
+ }
+ return newFixedLengthResponse(
+ NanoHTTPD.Response.Status.OK,
+ mimeType,
+ is,
+ is.available());
+ } catch (IOException e) {
+ return ErrorResponses.response404;
+ }
+ }
+ return ErrorResponses.response500;
+ }
+
+ private String parseMimeType(String url) {
+ String type = null;
+ if (url.endsWith(".js")) return "text/javascript";
+ String extension = MimeTypeMap.getFileExtensionFromUrl(url);
+ if (extension != null) {
+ type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ }
+ return type;
+ }
+
+ private String readHTMLFromFile(String filename) {
+ StringBuilder sb = new StringBuilder();
+ try {
+ InputStream is = androidContext.getAssets().open(FOLDER_NAME + filename);
+ BufferedReader br = new BufferedReader(new InputStreamReader(is,
+ Charset.forName("UTF-8")));
+ String str;
+ while ((str = br.readLine()) != null) {
+ sb.append(str);
+ }
+ br.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ return sb.toString();
+ }
+
+ public String getHtmlPage(String name) {
+ String responseText = "";
+ if (androidContext != null) {
+ responseText = readHTMLFromFile(name);
+ }
+ if (responseText == null) {
+ return null;
+ }
+ return responseText;
+ }
+
+ private String getFavoritesJson() {
+ List points = androidContext.getFavorites().getFavouritePoints();
+ StringBuilder text = new StringBuilder();
+ for (FavouritePoint p : points) {
+ String json = jsonFromFavorite(p);
+ text.append(json);
+ text.append(",");
+ }
+ return "[" + text.substring(0, text.length() - 1) + "]";
+ }
+
+ private String jsonFromFavorite(FavouritePoint favouritePoint) {
+ return gson.toJson(favouritePoint);
+ }
+
+ @Override
+ public void onDraw(RotatedTileBox viewport, Bitmap bmp) {
+ this.map.put(viewport, bmp);
+ }
+
+ static class ErrorResponses {
+ static NanoHTTPD.Response response404 =
+ newFixedLengthResponse(NanoHTTPD.Response.Status.NOT_FOUND,
+ NanoHTTPD.MIME_PLAINTEXT, "404 Not Found");
+
+ static NanoHTTPD.Response response500 =
+ newFixedLengthResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR,
+ NanoHTTPD.MIME_PLAINTEXT, "500 Internal Server Error");
+ }
+}
\ No newline at end of file
diff --git a/OsmAnd/src/net/osmand/plus/server/ServerSessionHandler.java b/OsmAnd/src/net/osmand/plus/server/ServerSessionHandler.java
new file mode 100644
index 0000000000..13d54fcab8
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/server/ServerSessionHandler.java
@@ -0,0 +1,30 @@
+package net.osmand.plus.server;
+
+import net.osmand.plus.OsmandApplication;
+
+import fi.iki.elonen.NanoHTTPD;
+import net.osmand.plus.activities.MapActivity;
+
+public class ServerSessionHandler {
+ private OsmandApplication androidApplication;
+
+ private ApiRouter router = new ApiRouter();
+
+ public OsmandApplication getAndroidApplication() {
+ return androidApplication;
+ }
+
+ public void setAndroidApplication(OsmandApplication androidApplication) {
+ this.androidApplication = androidApplication;
+ router.setAndroidContext(androidApplication);
+ }
+
+ public void setMapActivity(MapActivity activity) {
+ //todo
+ router.mapActivity = activity;
+ }
+
+ public NanoHTTPD.Response handle(NanoHTTPD.IHTTPSession session) {
+ return router.route(session);
+ }
+}
diff --git a/OsmAnd/src/net/osmand/plus/server/map/LayersDraw.java b/OsmAnd/src/net/osmand/plus/server/map/LayersDraw.java
new file mode 100644
index 0000000000..77997d48df
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/server/map/LayersDraw.java
@@ -0,0 +1,124 @@
+package net.osmand.plus.server.map;
+
+import android.graphics.Canvas;
+import net.osmand.data.RotatedTileBox;
+import net.osmand.map.ITileSource;
+import net.osmand.map.TileSourceManager;
+import net.osmand.plus.OsmandApplication;
+import net.osmand.plus.render.MapVectorLayer;
+import net.osmand.plus.routing.RoutingHelper;
+import net.osmand.plus.views.MapTileLayer;
+import net.osmand.plus.views.layers.FavouritesLayer;
+import net.osmand.plus.views.layers.GPXLayer;
+import net.osmand.plus.views.layers.MapTextLayer;
+import net.osmand.plus.views.layers.RouteLayer;
+
+public class LayersDraw {
+ public static void createLayers(OsmandApplication app, Canvas canvas, final OsmandMapTileMiniView mapView) {
+ RoutingHelper routingHelper = app.getRoutingHelper();
+ // first create to make accessible
+ MapTextLayer mapTextLayer = new MapTextLayer();
+ // 5.95 all labels
+ //mapView.addLayer(mapTextLayer, 5.95f);
+ // 8. context menu layer
+ //ContextMenuLayer contextMenuLayer = new ContextMenuLayer(app);
+ //mapView.addLayer(contextMenuLayer, 8);
+ // mapView.addLayer(underlayLayer, -0.5f);
+ RotatedTileBox currentTileBlock = mapView.getCurrentRotatedTileBox();
+// RotatedTileBox currentTileBlock = new RotatedTileBox.RotatedTileBoxBuilder()
+// .setLocation(50.901430, 34.801775)
+// .setZoom(15)
+// .setPixelDimensions(canvas.getWidth(), canvas.getHeight(), 0.5f, 0.5f).build();
+
+ MapTileMiniLayer mapTileLayer = new MapTileMiniLayer(true);
+ mapView.addLayer(mapTileLayer, 0.0f);
+
+ ITileSource map = TileSourceManager.getMapillaryVectorSource();
+ mapTileLayer.setMap(map);
+ mapTileLayer.drawTileMap(canvas,currentTileBlock);
+ //mapView.setMainLayer(mapTileLayer);
+ // 0.5 layer
+ MapVectorMiniLayer mapVectorLayer = new MapVectorMiniLayer(mapTileLayer, false);
+ mapView.addLayer(mapVectorLayer, 0.5f);
+// mapVectorLayer.onPrepareBufferImage(canvas,
+// currentTileBlock,
+// new OsmandMapMiniLayer.DrawSettings(false));
+ //DownloadedRegionsLayer downloadedRegionsLayer = new DownloadedRegionsLayer(activity);
+ //mapView.addLayer(downloadedRegionsLayer, 0.5f);
+
+ // 0.9 gpx layer
+ GPXLayer gpxLayer = new GPXLayer();
+ //mapView.addLayer(gpxLayer, 0.9f);
+
+ // 1. route layer
+ RouteLayer routeLayer = new RouteLayer(routingHelper);
+ //mapView.addLayer(routeLayer, 1);
+
+ // 2. osm bugs layer
+ // 3. poi layer
+ POIMapLayerMini poiMapLayer = new POIMapLayerMini(app);
+ mapView.addLayer(poiMapLayer, 3);
+
+ poiMapLayer.onPrepareBufferImage(canvas, currentTileBlock,
+ new OsmandMapMiniLayer.DrawSettings(false));
+ // 4. favorites layer
+ FavouritesLayer mFavouritesLayer = new FavouritesLayer();
+ //mapView.addLayer(mFavouritesLayer, 4);
+ // 4.6 measurement tool layer
+ //MeasurementToolLayer measurementToolLayer = new MeasurementToolLayer();
+ //mapView.addLayer(measurementToolLayer, 4.6f);
+ // 5. transport layer
+ //TransportStopsLayer transportStopsLayer = new TransportStopsLayer(activity);
+ //mapView.addLayer(transportStopsLayer, 5);
+ // 5.95 all text labels
+ // 6. point location layer
+ //PointLocationLayer locationLayer = new PointLocationLayer(activity.getMapViewTrackingUtilities());
+ //mapView.addLayer(locationLayer, 6);
+ // 7. point navigation layer
+ //PointNavigationLayer navigationLayer = new PointNavigationLayer(activity);
+ //mapView.addLayer(navigationLayer, 7);
+ // 7.3 map markers layer
+ //MapMarkersMiniLayer mapMarkersLayer = new MapMarkersMiniLayer(app);
+ //mapView.addLayer(mapMarkersLayer, 7.3f);
+ //MyCustomLayer layer = new MyCustomLayer();
+ //mapView.addLayer(layer,2);
+
+ // 7.5 Impassible roads
+ //ImpassableRoadsLayer impassableRoadsLayer = new ImpassableRoadsLayer(activity);
+ //mapView.addLayer(impassableRoadsLayer, 7.5f);
+ // 7.8 ruler control layer
+ //RulerControlLayer rulerControlLayer = new RulerControlLayer(activity);
+ //mapView.addLayer(rulerControlLayer, 7.8f);
+ // 8. context menu layer
+ // 9. map info layer
+ //MapInfoLayer mapInfoLayer = new MapInfoLayer(activity, routeLayer);
+ //mapView.addLayer(mapInfoLayer, 9);
+ // 11. route info layer
+ //MapControlsLayer mapControlsLayer = new MapControlsLayer(activity);
+ //mapView.addLayer(mapControlsLayer, 11);
+ // 12. quick actions layer
+ //MapQuickActionLayer mapQuickActionLayer = new MapQuickActionLayer(activity, contextMenuLayer);
+ //mapView.addLayer(mapQuickActionLayer, 12);
+ //contextMenuLayer.setMapQuickActionLayer(mapQuickActionLayer);
+ //mapControlsLayer.setMapQuickActionLayer(mapQuickActionLayer);
+
+// StateChangedListener transparencyListener = new StateChangedListener() {
+// @Override
+// public void stateChanged(Integer change) {
+// mapTileLayer.setAlpha(change);
+// mapVectorLayer.setAlpha(change);
+// mapView.refreshMap();
+// }
+// };
+ // app.getSettings().MAP_TRANSPARENCY.addListener(transparencyListener);
+
+
+ //OsmandPlugin.createLayers(mapView, activity);
+ //app.getAppCustomization().createLayers(mapView, activity);
+ //app.getAidlApi().registerMapLayers(activity);
+
+ //return OsmandPlugin.createLayers(mapView, app);
+ //app.getAppCustomization().createLayers(mapView, activity);
+ //app.getAidlApi().registerMapLayers(activity);
+ }
+}
diff --git a/OsmAnd/src/net/osmand/plus/server/map/MapMarkersMiniLayer.java b/OsmAnd/src/net/osmand/plus/server/map/MapMarkersMiniLayer.java
new file mode 100644
index 0000000000..31161b5d52
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/server/map/MapMarkersMiniLayer.java
@@ -0,0 +1,646 @@
+package net.osmand.plus.server.map;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.PointF;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import com.google.android.material.snackbar.Snackbar;
+
+import net.osmand.GPXUtilities.TrkSegment;
+import net.osmand.Location;
+import net.osmand.data.Amenity;
+import net.osmand.data.LatLon;
+import net.osmand.data.PointDescription;
+import net.osmand.data.QuadPoint;
+import net.osmand.data.RotatedTileBox;
+import net.osmand.plus.MapMarkersHelper;
+import net.osmand.plus.MapMarkersHelper.MapMarker;
+import net.osmand.plus.OsmAndConstants;
+import net.osmand.plus.OsmAndFormatter;
+import net.osmand.plus.OsmandApplication;
+import net.osmand.plus.settings.backend.OsmandSettings;
+import net.osmand.plus.R;
+import net.osmand.plus.TargetPointsHelper.TargetPoint;
+import net.osmand.plus.activities.MapActivity;
+import net.osmand.plus.base.MapViewTrackingUtilities;
+import net.osmand.plus.views.OsmandMapLayer;
+import net.osmand.plus.views.OsmandMapTileView;
+import net.osmand.plus.views.Renderable;
+import net.osmand.plus.views.layers.ContextMenuLayer;
+import net.osmand.plus.views.layers.ContextMenuLayer.ApplyMovedObjectCallback;
+import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider;
+import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProviderSelection;
+import net.osmand.plus.views.layers.geometry.GeometryWay;
+import net.osmand.plus.views.mapwidgets.MapMarkersWidgetsFactory;
+import net.osmand.util.MapUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class MapMarkersMiniLayer extends OsmandMapMiniLayer implements IContextMenuProvider,
+ IContextMenuProviderSelection, ContextMenuLayer.IMoveObjectProvider {
+
+ private static final long USE_FINGER_LOCATION_DELAY = 1000;
+ private static final int MAP_REFRESH_MESSAGE = OsmAndConstants.UI_HANDLER_MAP_VIEW + 6;
+ protected static final int DIST_TO_SHOW = 80;
+ private OsmandApplication application;
+
+ private OsmandMapTileMiniView view;
+
+ private MapMarkersWidgetsMiniFactory widgetsFactory;
+
+ private Paint bitmapPaint;
+ private Bitmap markerBitmapBlue;
+ private Bitmap markerBitmapGreen;
+ private Bitmap markerBitmapOrange;
+ private Bitmap markerBitmapRed;
+ private Bitmap markerBitmapYellow;
+ private Bitmap markerBitmapTeal;
+ private Bitmap markerBitmapPurple;
+
+ private Paint bitmapPaintDestBlue;
+ private Paint bitmapPaintDestGreen;
+ private Paint bitmapPaintDestOrange;
+ private Paint bitmapPaintDestRed;
+ private Paint bitmapPaintDestYellow;
+ private Paint bitmapPaintDestTeal;
+ private Paint bitmapPaintDestPurple;
+ private Bitmap arrowLight;
+ private Bitmap arrowToDestination;
+ private Bitmap arrowShadow;
+ private float[] calculations = new float[2];
+
+ private final TextPaint textPaint = new TextPaint();
+ private final RenderingLineAttributes lineAttrs = new RenderingLineAttributes("measureDistanceLine");
+ private final RenderingLineAttributes textAttrs = new RenderingLineAttributes("rulerLineFont");
+ private final RenderingLineAttributes planRouteAttrs = new RenderingLineAttributes("markerPlanRouteline");
+ private TrkSegment route;
+
+ private float textSize;
+ private int verticalOffset;
+
+ private List tx = new ArrayList<>();
+ private List ty = new ArrayList<>();
+ private Path linePath = new Path();
+
+ private LatLon fingerLocation;
+ private boolean hasMoved;
+ private boolean moving;
+ private boolean useFingerLocation;
+ private GestureDetector longTapDetector;
+ private Handler handler;
+
+ private ContextMenuLayer contextMenuLayer;
+
+ private boolean inPlanRouteMode;
+ private boolean defaultAppMode = true;
+
+ private List amenities = new ArrayList<>();
+
+ public MapMarkersMiniLayer(OsmandApplication app) {
+ this.application = app;
+ }
+
+ public MapMarkersWidgetsMiniFactory getWidgetsFactory() {
+ return widgetsFactory;
+ }
+
+ public boolean isInPlanRouteMode() {
+ return inPlanRouteMode;
+ }
+
+ public void setInPlanRouteMode(boolean inPlanRouteMode) {
+ this.inPlanRouteMode = inPlanRouteMode;
+ }
+
+ public void setDefaultAppMode(boolean defaultAppMode) {
+ this.defaultAppMode = defaultAppMode;
+ }
+
+ private void initUI() {
+ bitmapPaint = new Paint();
+ bitmapPaint.setAntiAlias(true);
+ bitmapPaint.setFilterBitmap(true);
+ markerBitmapBlue = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_blue);
+ markerBitmapGreen = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_green);
+ markerBitmapOrange = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_orange);
+ markerBitmapRed = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_red);
+ markerBitmapYellow = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_yellow);
+ markerBitmapTeal = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_teal);
+ markerBitmapPurple = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_purple);
+
+ arrowLight = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_direction_arrow_p1_light);
+ arrowToDestination = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_direction_arrow_p2_color);
+ arrowShadow = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_direction_arrow_p3_shadow);
+ bitmapPaintDestBlue = createPaintDest(R.color.marker_blue);
+ bitmapPaintDestGreen = createPaintDest(R.color.marker_green);
+ bitmapPaintDestOrange = createPaintDest(R.color.marker_orange);
+ bitmapPaintDestRed = createPaintDest(R.color.marker_red);
+ bitmapPaintDestYellow = createPaintDest(R.color.marker_yellow);
+ bitmapPaintDestTeal = createPaintDest(R.color.marker_teal);
+ bitmapPaintDestPurple = createPaintDest(R.color.marker_purple);
+
+ widgetsFactory = new MapMarkersWidgetsMiniFactory(application);
+
+ //contextMenuLayer = view.getLayerByClass(ContextMenuLayer.class);
+
+ textSize = application.getResources().getDimensionPixelSize(R.dimen.guide_line_text_size);
+ verticalOffset = application.getResources().getDimensionPixelSize(R.dimen.guide_line_vertical_offset);
+ }
+
+ private Paint createPaintDest(int colorId) {
+ Paint paint = new Paint();
+ paint.setDither(true);
+ paint.setAntiAlias(true);
+ paint.setFilterBitmap(true);
+ int color = ContextCompat.getColor(application, colorId);
+ paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
+ return paint;
+ }
+
+ private Paint getMarkerDestPaint(int colorIndex) {
+ switch (colorIndex) {
+ case 0:
+ return bitmapPaintDestBlue;
+ case 1:
+ return bitmapPaintDestGreen;
+ case 2:
+ return bitmapPaintDestOrange;
+ case 3:
+ return bitmapPaintDestRed;
+ case 4:
+ return bitmapPaintDestYellow;
+ case 5:
+ return bitmapPaintDestTeal;
+ case 6:
+ return bitmapPaintDestPurple;
+ default:
+ return bitmapPaintDestBlue;
+ }
+ }
+
+ private Bitmap getMapMarkerBitmap(int colorIndex) {
+ switch (colorIndex) {
+ case 0:
+ return markerBitmapBlue;
+ case 1:
+ return markerBitmapGreen;
+ case 2:
+ return markerBitmapOrange;
+ case 3:
+ return markerBitmapRed;
+ case 4:
+ return markerBitmapYellow;
+ case 5:
+ return markerBitmapTeal;
+ case 6:
+ return markerBitmapPurple;
+ default:
+ return markerBitmapBlue;
+ }
+ }
+
+ public void setRoute(TrkSegment route) {
+ this.route = route;
+ }
+
+ @Override
+ public void initLayer(OsmandMapTileMiniView view) {
+ this.view = view;
+ //handler = new Handler();
+ initUI();
+// longTapDetector = new GestureDetector(view.getContext(), new GestureDetector.SimpleOnGestureListener() {
+// @Override
+// public void onLongPress(MotionEvent e) {
+// cancelFingerAction();
+// }
+// });
+ }
+
+ @Override
+ public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings nightMode) {
+ OsmandApplication app = application;
+ OsmandSettings settings = app.getSettings();
+ if (!settings.SHOW_MAP_MARKERS.get()) {
+ return;
+ }
+
+ Location myLoc;
+ if (useFingerLocation && fingerLocation != null) {
+ myLoc = new Location("");
+ myLoc.setLatitude(fingerLocation.getLatitude());
+ myLoc.setLongitude(fingerLocation.getLongitude());
+ } else {
+ myLoc = app.getLocationProvider().getLastStaleKnownLocation();
+ }
+ MapMarkersHelper markersHelper = app.getMapMarkersHelper();
+ List activeMapMarkers = markersHelper.getMapMarkers();
+ int displayedWidgets = settings.DISPLAYED_MARKERS_WIDGETS_COUNT.get();
+
+ if (route != null && route.points.size() > 0) {
+ planRouteAttrs.updatePaints(app, nightMode, tileBox);
+ new Renderable.StandardTrack(new ArrayList<>(route.points), 17.2).
+ drawSegment(view.getZoom(), defaultAppMode ? planRouteAttrs.paint : planRouteAttrs.paint2, canvas, tileBox);
+ }
+
+ if (settings.SHOW_LINES_TO_FIRST_MARKERS.get() && myLoc != null) {
+ textAttrs.paint.setTextSize(textSize);
+ textAttrs.paint2.setTextSize(textSize);
+
+ lineAttrs.updatePaints(app, nightMode, tileBox);
+ textAttrs.updatePaints(app, nightMode, tileBox);
+ textAttrs.paint.setStyle(Paint.Style.FILL);
+
+ textPaint.set(textAttrs.paint);
+
+ boolean drawMarkerName = settings.DISPLAYED_MARKERS_WIDGETS_COUNT.get() == 1;
+
+ float locX;
+ float locY;
+ if (app.getMapViewTrackingUtilities().isMapLinkedToLocation()
+ && !MapViewTrackingUtilities.isSmallSpeedForAnimation(myLoc)
+ && !app.getMapViewTrackingUtilities().isMovingToMyLocation()) {
+ locX = tileBox.getPixXFromLatLon(tileBox.getLatitude(), tileBox.getLongitude());
+ locY = tileBox.getPixYFromLatLon(tileBox.getLatitude(), tileBox.getLongitude());
+ } else {
+ locX = tileBox.getPixXFromLatLon(myLoc.getLatitude(), myLoc.getLongitude());
+ locY = tileBox.getPixYFromLatLon(myLoc.getLatitude(), myLoc.getLongitude());
+ }
+ int[] colors = MapMarker.getColors(application);
+ for (int i = 0; i < activeMapMarkers.size() && i < displayedWidgets; i++) {
+ MapMarker marker = activeMapMarkers.get(i);
+ float markerX = tileBox.getPixXFromLatLon(marker.getLatitude(), marker.getLongitude());
+ float markerY = tileBox.getPixYFromLatLon(marker.getLatitude(), marker.getLongitude());
+
+ linePath.reset();
+ tx.clear();
+ ty.clear();
+
+ tx.add(locX);
+ ty.add(locY);
+ tx.add(markerX);
+ ty.add(markerY);
+
+ GeometryWay.calculatePath(tileBox, tx, ty, linePath);
+ PathMeasure pm = new PathMeasure(linePath, false);
+ float[] pos = new float[2];
+ pm.getPosTan(pm.getLength() / 2, pos, null);
+
+ float dist = (float) MapUtils.getDistance(myLoc.getLatitude(), myLoc.getLongitude(), marker.getLatitude(), marker.getLongitude());
+ String distSt = OsmAndFormatter.getFormattedDistance(dist, view.getApplication());
+ String text = distSt + (drawMarkerName ? " • " + marker.getName(application) : "");
+ text = TextUtils.ellipsize(text, textPaint, pm.getLength(), TextUtils.TruncateAt.END).toString();
+ Rect bounds = new Rect();
+ textAttrs.paint.getTextBounds(text, 0, text.length(), bounds);
+ float hOffset = pm.getLength() / 2 - bounds.width() / 2;
+ lineAttrs.paint.setColor(colors[marker.colorIndex]);
+
+ canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
+ canvas.drawPath(linePath, lineAttrs.paint);
+ if (locX >= markerX) {
+ canvas.rotate(180, pos[0], pos[1]);
+ canvas.drawTextOnPath(text, linePath, hOffset, bounds.height() + verticalOffset, textAttrs.paint2);
+ canvas.drawTextOnPath(text, linePath, hOffset, bounds.height() + verticalOffset, textAttrs.paint);
+ canvas.rotate(-180, pos[0], pos[1]);
+ } else {
+ canvas.drawTextOnPath(text, linePath, hOffset, -verticalOffset, textAttrs.paint2);
+ canvas.drawTextOnPath(text, linePath, hOffset, -verticalOffset, textAttrs.paint);
+ }
+ canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY());
+ }
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings nightMode) {
+ //widgetsFactory.updateInfo(useFingerLocation ? fingerLocation : null, tileBox.getZoom());
+ OsmandSettings settings = application.getSettings();
+
+ if (tileBox.getZoom() < 3 || !settings.SHOW_MAP_MARKERS.get()) {
+ return;
+ }
+
+ int displayedWidgets = settings.DISPLAYED_MARKERS_WIDGETS_COUNT.get();
+
+ MapMarkersHelper markersHelper = application.getMapMarkersHelper();
+
+ for (MapMarker marker : markersHelper.getMapMarkers()) {
+ if (isLocationVisible(tileBox, marker) && !overlappedByWaypoint(marker)
+ && !isInMotion(marker) && !isSynced(marker)) {
+ Bitmap bmp = getMapMarkerBitmap(marker.colorIndex);
+ int marginX = bmp.getWidth() / 6;
+ int marginY = bmp.getHeight();
+ int locationX = tileBox.getPixXFromLonNoRot(marker.getLongitude());
+ int locationY = tileBox.getPixYFromLatNoRot(marker.getLatitude());
+ canvas.rotate(-tileBox.getRotate(), locationX, locationY);
+ canvas.drawBitmap(bmp, locationX - marginX, locationY - marginY, bitmapPaint);
+ canvas.rotate(tileBox.getRotate(), locationX, locationY);
+ }
+ }
+
+ if (settings.SHOW_ARROWS_TO_FIRST_MARKERS.get()) {
+ LatLon loc = tileBox.getCenterLatLon();
+ int i = 0;
+ for (MapMarker marker : markersHelper.getMapMarkers()) {
+ if (!isLocationVisible(tileBox, marker) && !isInMotion(marker)) {
+ canvas.save();
+ net.osmand.Location.distanceBetween(loc.getLatitude(), loc.getLongitude(),
+ marker.getLatitude(), marker.getLongitude(), calculations);
+ float bearing = calculations[1] - 90;
+ float radiusBearing = DIST_TO_SHOW * tileBox.getDensity();
+ final QuadPoint cp = tileBox.getCenterPixelPoint();
+ canvas.rotate(bearing, cp.x, cp.y);
+ canvas.translate(-24 * tileBox.getDensity() + radiusBearing, -22 * tileBox.getDensity());
+ canvas.drawBitmap(arrowShadow, cp.x, cp.y, bitmapPaint);
+ canvas.drawBitmap(arrowToDestination, cp.x, cp.y, getMarkerDestPaint(marker.colorIndex));
+ canvas.drawBitmap(arrowLight, cp.x, cp.y, bitmapPaint);
+ canvas.restore();
+ }
+ i++;
+ if (i > displayedWidgets - 1) {
+ break;
+ }
+ }
+ }
+
+// if (contextMenuLayer.getMoveableObject() instanceof MapMarker) {
+// MapMarker objectInMotion = (MapMarker) contextMenuLayer.getMoveableObject();
+// PointF pf = contextMenuLayer.getMovableCenterPoint(tileBox);
+// Bitmap bitmap = getMapMarkerBitmap(objectInMotion.colorIndex);
+// int marginX = bitmap.getWidth() / 6;
+// int marginY = bitmap.getHeight();
+// float locationX = pf.x;
+// float locationY = pf.y;
+// canvas.rotate(-tileBox.getRotate(), locationX, locationY);
+// canvas.drawBitmap(bitmap, locationX - marginX, locationY - marginY, bitmapPaint);
+//
+// }
+ }
+
+ private boolean isSynced(@NonNull MapMarker marker) {
+ return marker.wptPt != null || marker.favouritePoint != null;
+ }
+
+ private boolean isInMotion(@NonNull MapMarker marker) {
+ return marker.equals(contextMenuLayer.getMoveableObject());
+ }
+
+ public boolean isLocationVisible(RotatedTileBox tb, MapMarker marker) {
+ //noinspection SimplifiableIfStatement
+ if (marker == null || tb == null) {
+ return false;
+ }
+ return containsLatLon(tb, marker.getLatitude(), marker.getLongitude());
+ }
+
+ public boolean containsLatLon(RotatedTileBox tb, double lat, double lon) {
+ double widgetHeight = 0;
+ if (widgetsFactory.isTopBarVisible()) {
+ widgetHeight = widgetsFactory.getTopBarHeight();
+ }
+ double tx = tb.getPixXFromLatLon(lat, lon);
+ double ty = tb.getPixYFromLatLon(lat, lon);
+ return tx >= 0 && tx <= tb.getPixWidth() && ty >= widgetHeight && ty <= tb.getPixHeight();
+ }
+
+ public boolean overlappedByWaypoint(MapMarker marker) {
+ List targetPoints = application.getTargetPointsHelper().getAllPoints();
+ for (TargetPoint t : targetPoints) {
+ if (t.point.equals(marker.point)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void destroyLayer() {
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event, RotatedTileBox tileBox) {
+ if (!longTapDetector.onTouchEvent(event)) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ float x = event.getX();
+ float y = event.getY();
+ fingerLocation = tileBox.getLatLonFromPixel(x, y);
+ hasMoved = false;
+ moving = true;
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (!hasMoved) {
+ if (!handler.hasMessages(MAP_REFRESH_MESSAGE)) {
+ Message msg = Message.obtain(handler, new Runnable() {
+ @Override
+ public void run() {
+ handler.removeMessages(MAP_REFRESH_MESSAGE);
+ if (moving) {
+ if (!useFingerLocation) {
+ useFingerLocation = true;
+ }
+ }
+ }
+ });
+ msg.what = MAP_REFRESH_MESSAGE;
+ handler.sendMessageDelayed(msg, USE_FINGER_LOCATION_DELAY);
+ }
+ hasMoved = true;
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ cancelFingerAction();
+ break;
+ }
+ }
+ return super.onTouchEvent(event, tileBox);
+ }
+
+ private void cancelFingerAction() {
+ handler.removeMessages(MAP_REFRESH_MESSAGE);
+ useFingerLocation = false;
+ moving = false;
+ fingerLocation = null;
+ }
+
+ @Override
+ public boolean drawInScreenPixels() {
+ return false;
+ }
+
+ @Override
+ public boolean disableSingleTap() {
+ return inPlanRouteMode;
+ }
+
+ @Override
+ public boolean disableLongPressOnMap() {
+ return inPlanRouteMode;
+ }
+
+ @Override
+ public boolean isObjectClickable(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean runExclusiveAction(Object o, boolean unknownLocation) {
+ OsmandSettings settings = application.getSettings();
+ if (unknownLocation
+ || o == null
+ || !(o instanceof MapMarker)
+ || !settings.SELECT_MARKER_ON_SINGLE_TAP.get()
+ || !settings.SHOW_MAP_MARKERS.get()) {
+ return false;
+ }
+ final MapMarkersHelper helper = application.getMapMarkersHelper();
+ final MapMarker old = helper.getMapMarkers().get(0);
+ helper.moveMarkerToTop((MapMarker) o);
+ String title = application.getString(R.string.marker_activated, helper.getMapMarkers().get(0).getName(application));
+ return true;
+ }
+
+ @Override
+ public void collectObjectsFromPoint(PointF point, RotatedTileBox tileBox, List