diff --git a/.project b/.project new file mode 100644 index 000000000..f564041e5 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + react-native-maps + Project react-native-maps created by Buildship. + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 000000000..03931c0c1 --- /dev/null +++ b/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,3 @@ +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) +connection.project.dir= +eclipse.preferences.version=1 diff --git a/build.gradle b/build.gradle index 8a3e2453e..588e5cb99 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.1' + classpath 'com.android.tools.build:gradle:2.3.2' } } diff --git a/example/App.js b/example/App.js index dadbea056..793a0f809 100644 --- a/example/App.js +++ b/example/App.js @@ -123,6 +123,7 @@ class App extends React.Component { render() { return this.renderExamples([ // [, , , ] + [CustomTiles, 'Custom Tiles', true], [StaticMap, 'StaticMap', true], [DisplayLatLng, 'Tracking Position', true, '(incomplete)'], [ViewsAsMarkers, 'Arbitrary Views as Markers', true], @@ -143,7 +144,6 @@ class App extends React.Component { [FitToSuppliedMarkers, 'Focus Map On Markers', true], [FitToCoordinates, 'Fit Map To Coordinates', true], [LiteMapView, 'Android Lite MapView'], - [CustomTiles, 'Custom Tiles', true], [ZIndexMarkers, 'Position Markers with Z-index', true], [MapStyle, 'Customize the style of the map', true], [LegalLabel, 'Reposition the legal label', true], diff --git a/example/android/app/.classpath b/example/android/app/.classpath new file mode 100644 index 000000000..8d8d85f14 --- /dev/null +++ b/example/android/app/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/example/android/app/.project b/example/android/app/.project new file mode 100644 index 000000000..ca3856c62 --- /dev/null +++ b/example/android/app/.project @@ -0,0 +1,23 @@ + + + example-android + Project example-android created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/example/android/app/.settings/org.eclipse.buildship.core.prefs b/example/android/app/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 000000000..0d766b113 --- /dev/null +++ b/example/android/app/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,3 @@ +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) +connection.project.dir=../../.. +eclipse.preferences.version=1 diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index b111eef4a..659a1fcdc 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,9 @@ + + + { + this.updateRegion(region); + } + + onRegionChange = (region) => { + if (this.onRegionChangeTimer) { + clearTimeout(this.onRegionChangeTimer); + } + this.onRegionChangeTimer = setTimeout(() => { + this.updateRegion(region); + }, 200); + } + + onMapPress = (e) => { + const coordinates = e.nativeEvent.coordinate; + const zoom = this.getMapZoom(); + const tile = tilebelt.pointToTile(coordinates.longitude, coordinates.latitude, zoom, true); + + this.setState({ + coordinates: { + tile: [tile[0], tile[1], tile[2]], + precision: [tile[3], tile[4]], + }, + }); + } + + updateRegion = (region) => { + this.setState({ region }); + } + render() { - const { region } = this.state; + const { region, coordinates } = this.state; + const hasCoordinates = (coordinates.tile && coordinates.tile.length === 3) || false; + return ( - + {hasCoordinates && + + } diff --git a/lib/android/.classpath b/lib/android/.classpath new file mode 100644 index 000000000..8d8d85f14 --- /dev/null +++ b/lib/android/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/lib/android/.project b/lib/android/.project new file mode 100644 index 000000000..2a84911fc --- /dev/null +++ b/lib/android/.project @@ -0,0 +1,23 @@ + + + react-native-maps-lib + Project react-native-maps-lib created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/lib/android/.settings/org.eclipse.buildship.core.prefs b/lib/android/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 000000000..abb1f3437 --- /dev/null +++ b/lib/android/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,3 @@ +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) +connection.project.dir=../.. +eclipse.preferences.version=1 diff --git a/lib/android/gradle.properties b/lib/android/gradle.properties index 65c67798d..307005f30 100644 --- a/lib/android/gradle.properties +++ b/lib/android/gradle.properties @@ -16,3 +16,4 @@ POM_DEVELOPER_NAME=Leland Richardson POM_NAME=ReactNative Maps library POM_ARTIFACT_ID=react-native-maps POM_PACKAGING=aar +org.gradle.jvmargs=-Xmx2048m diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasFeature.java b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasFeature.java new file mode 100644 index 000000000..ba1b877dc --- /dev/null +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasFeature.java @@ -0,0 +1,131 @@ +package com.airbnb.android.react.maps; + +import android.content.Context; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.model.TileOverlay; +import com.google.android.gms.maps.model.TileOverlayOptions; + +/** + * Created by joseangel.parreno@vizzuality.com on 25/05/2017. + */ + +public abstract class AirMapCanvasFeature extends AirMapFeature { + protected TileOverlayOptions tileOverlayOptions; + protected TileOverlay tileOverlay; + protected AirMapCanvasTileProvider tileProvider; + + protected String urlTemplate; + protected int maxZoom; + protected String areaId; + protected String alertType; + protected String minDate; + protected String maxDate; + protected boolean isConnected; + protected float zIndex; + protected Coordinates coordinates; + + public AirMapCanvasFeature(Context context) { + super(context); + } + + public void setUrlTemplate(String urlTemplate) { + this.urlTemplate = urlTemplate; + if (tileProvider != null) { + tileProvider.setUrlTemplate(urlTemplate); + } + if (tileOverlay != null) { + tileOverlay.clearTileCache(); + } + } + + public void setMaxZoom(int maxZoom) { + this.maxZoom = maxZoom; + if (tileProvider != null) { + tileProvider.setMaxZoom(maxZoom); + } + if (tileOverlay != null) { + tileOverlay.clearTileCache(); + } + } + + public void setAreaId(String areaId) { + this.areaId = areaId; + if (tileProvider != null) { + tileProvider.setAreaId(areaId); + } + if (tileOverlay != null) { + tileOverlay.clearTileCache(); + } + } + + public void setIsConnected(boolean isConnected) { + this.isConnected = isConnected; + if (tileProvider != null) { + tileProvider.setIsConnected(isConnected); + } + if (tileOverlay != null) { + tileOverlay.clearTileCache(); + } + } + + public void setAlertType(String alertType) { + this.alertType = alertType; + if (tileProvider != null) { + tileProvider.setAlertType(alertType); + } + if (tileOverlay != null) { + tileOverlay.clearTileCache(); + } + } + + public void setMinDate(String minDate) { + this.minDate = minDate; + } + + public void setMaxDate(String maxDate) { + this.maxDate = maxDate; + } + + public void setZIndex(float zIndex) { + this.zIndex = zIndex; + if (tileOverlay != null) { + tileOverlay.setZIndex(zIndex); + } + } + + public void setCoordinates(Coordinates coordinates) { + this.coordinates = coordinates; + if (tileProvider != null) { + tileProvider.setCoordinates(coordinates); + } + if (tileOverlay != null) { + tileOverlay.clearTileCache(); + } + } + + public TileOverlayOptions getTileOverlayOptions() { + if (tileOverlayOptions == null) { + tileOverlayOptions = createTileOverlayOptions(); + } + return tileOverlayOptions; + } + + protected abstract TileOverlayOptions createTileOverlayOptions(); + + + @Override + public Object getFeature() { + return tileOverlay; + } + + @Override + public void addToMap(GoogleMap map) { + this.tileOverlay = map.addTileOverlay(getTileOverlayOptions()); + } + + @Override + public void removeFromMap(GoogleMap map) { + tileOverlay.remove(); + } +} diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasInteractionUrlTile.java b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasInteractionUrlTile.java new file mode 100644 index 000000000..261bce835 --- /dev/null +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasInteractionUrlTile.java @@ -0,0 +1,82 @@ +package com.airbnb.android.react.maps; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import com.google.android.gms.maps.model.TileOverlayOptions; + + +public class AirMapCanvasInteractionUrlTile extends AirMapCanvasFeature { + + public AirMapCanvasInteractionUrlTile(Context context){ super(context); } + + class AIRMapCanvasInteractionTileProvider extends AirMapCanvasTileProvider { + + public AIRMapCanvasInteractionTileProvider(int width, int height, String urlTemplate, int maxZoom, String areaId, boolean isConnected, String minDate, String maxDate, String alertType, Coordinates coordinates) { + super(width, height, urlTemplate, maxZoom, areaId, isConnected, minDate, maxDate, alertType, coordinates); + } + + protected Context getParentContext() { + return getContext(); + } + + protected Bitmap paintTile(Bitmap scaledBitmap, int width, int height, int zoom, int zsteps, int minDate, int maxDate) { + Bitmap finalBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + int red, green, blue, alpha, c; + double[] precision = coordinates.getPrecision(); + int xFilter = (int)(precision[0] * width); + int yFilter = (int)(precision[1] * height); + double THRESHOLD = zoom * 0.5 * zsteps; + + + for(int xPoint=0; xPoint < width; xPoint++) { + for(int yPoint=0; yPoint < height; yPoint++) { + c = scaledBitmap.getPixel(xPoint, yPoint); + + red = Color.red(c); + green = Color.green(c); + blue = Color.blue(c); + + if (red > 255) red = 255; + if (green > 255) green = 255; + if (blue > 255) blue = 255; + + int day; + if (this.alertType != null && this.alertType.equals("viirs")) { + day = blue; + } else { + day = red * 255 + green; + } + + boolean inDay = day > 0 && day >= minDate && day <= maxDate; + boolean inXPoint = xPoint >= (xFilter - THRESHOLD) && (xPoint <= xFilter + THRESHOLD); + boolean inYPoint = yPoint >= (yFilter - THRESHOLD) && (yPoint <= yFilter + THRESHOLD); + + if (inDay && inXPoint && inYPoint) { + red = 255; + green = 255; + blue = 255; + alpha = 150; + } else { + alpha = 0; + } + + finalBitmap.setPixel(xPoint, yPoint, Color.argb(alpha, red, green, blue)); + } + } + return finalBitmap; + } + + + } + + + protected TileOverlayOptions createTileOverlayOptions() { + TileOverlayOptions options = new TileOverlayOptions(); + options.zIndex(zIndex); + this.tileProvider = new AIRMapCanvasInteractionTileProvider(256, 256, this.urlTemplate, this.maxZoom, this.areaId, this.isConnected, this.minDate, this.maxDate, this.alertType, this.coordinates); + options.tileProvider(this.tileProvider); + return options; + } + +} diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasInteractionUrlTileManager.java b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasInteractionUrlTileManager.java new file mode 100644 index 000000000..e4fc03957 --- /dev/null +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasInteractionUrlTileManager.java @@ -0,0 +1,84 @@ +package com.airbnb.android.react.maps; + +import android.content.Context; +import android.os.Build; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.WindowManager; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewGroupManager; +import com.facebook.react.uimanager.annotations.ReactProp; + +public class AirMapCanvasInteractionUrlTileManager extends ViewGroupManager { + private DisplayMetrics metrics; + + public AirMapCanvasInteractionUrlTileManager(ReactApplicationContext reactContext) { + super(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + metrics = new DisplayMetrics(); + ((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay() + .getRealMetrics(metrics); + } else { + metrics = reactContext.getResources().getDisplayMetrics(); + } + } + + @Override + public String getName() { + return "AIRMapCanvasInteractionUrlTile"; + } + + @Override + public AirMapCanvasInteractionUrlTile createViewInstance(ThemedReactContext context) { + return new AirMapCanvasInteractionUrlTile(context); + } + + @ReactProp(name = "urlTemplate") + public void setUrlTemplate(AirMapCanvasInteractionUrlTile view, String urlTemplate) { + view.setUrlTemplate(urlTemplate); + } + + @ReactProp(name = "maxZoom", defaultInt = 12) + public void setMaxZoom(AirMapCanvasInteractionUrlTile view, int maxZoom) { + view.setMaxZoom(maxZoom); + } + + @ReactProp(name = "areaId") + public void setAreaId(AirMapCanvasInteractionUrlTile view, String areaId) { + view.setAreaId(areaId); + } + + @ReactProp(name = "isConnected", defaultBoolean = true) + public void setIsConnected(AirMapCanvasInteractionUrlTile view, boolean isConnected) { + view.setIsConnected(isConnected); + } + + @ReactProp(name = "minDate") + public void setMinDate(AirMapCanvasInteractionUrlTile view, String minDate) { + view.setMinDate(minDate); + } + + @ReactProp(name = "maxDate") + public void setMaxDate(AirMapCanvasInteractionUrlTile view, String maxDate) { + view.setMaxDate(maxDate); + } + + @ReactProp(name = "zIndex", defaultFloat = -1.0f) + public void setZIndex(AirMapCanvasInteractionUrlTile view, float zIndex) { + view.setZIndex(zIndex); + } + + @ReactProp(name = "coordinates") + public void setCoordinates(AirMapCanvasInteractionUrlTile view, ReadableMap coordinates) { + if (coordinates != null) { + int[] tile = new int[]{coordinates.getArray("tile").getInt(0), coordinates.getArray("tile").getInt(1), coordinates.getArray("tile").getInt(2)}; + double[] precision = new double[]{coordinates.getArray("precision").getDouble(0), coordinates.getArray("precision").getDouble(1)}; + view.setCoordinates(new Coordinates(tile, precision)); + } + } + +} diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasTileProvider.java b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasTileProvider.java new file mode 100644 index 000000000..0f9be1ca0 --- /dev/null +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasTileProvider.java @@ -0,0 +1,180 @@ +package com.airbnb.android.react.maps; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.util.Log; + +import com.google.android.gms.maps.model.Tile; +import com.google.android.gms.maps.model.TileProvider; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.net.URL; + +/** + * Created by joseangel.parreno@vizzuality.com on 19/05/2017. + */ + + +public abstract class AirMapCanvasTileProvider implements TileProvider { + protected String urlTemplate; + protected int width; + protected int height; + protected int maxZoom; + protected String areaId; + protected String minDate; + protected String maxDate; + protected boolean isConnected; + protected Coordinates coordinates; + protected String alertType; + + public AirMapCanvasTileProvider(int width, int height, String urlTemplate, int maxZoom, String areaId, boolean isConnected, String minDate, String maxDate, String alertType, Coordinates coordinates) { + super(); + this.width = width; + this.height = height; + this.urlTemplate = urlTemplate; + this.maxZoom = maxZoom; + this.areaId = areaId; + this.minDate = minDate; + this.maxDate = maxDate; + this.alertType = alertType; + this.isConnected = isConnected; + this.coordinates = coordinates; + } + @Override + public Tile getTile(int x, int y, int zoom) { + int TILE_SIZE = this.width; + int maxZoom = 12; + int xCord = x; + int yCord = y; + int zoomCord = zoom; + int srcX = 0; + int srcY = 0; + int srcW = this.width; + int srcH = this.height; + int scaleSize = 1; + + int minDate = Integer.valueOf(this.minDate); + int maxDate = Integer.valueOf(this.maxDate); + + if (this.coordinates != null) { + int[] tile = this.coordinates.getTile(); + if(!(tile[0] == x && tile[1] == y && tile[2] == zoom)) { + return NO_TILE; + } + } + + if (zoom > this.maxZoom) { + xCord = (int)(x / (Math.pow(2, zoom - this.maxZoom))); + yCord = (int)(y / (Math.pow(2, zoom - this.maxZoom))); + zoomCord = this.maxZoom; + } + + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + byte[] bitmapData = stream.toByteArray(); + + Bitmap image; + + if (this.isConnected) { + String providerUrl = this.urlTemplate + .replace("{x}", Integer.toString(xCord)) + .replace("{y}", Integer.toString(yCord)) + .replace("{z}", Integer.toString(zoomCord)); + + URL url; + try { + url = new URL(providerUrl); + image = BitmapFactory.decodeStream(url.openConnection().getInputStream()); + } catch (Exception e) { + e.printStackTrace(); + return NO_TILE; + } + + } else { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + File dir = getParentContext().getFilesDir(); + File myFile = new File(dir + "/tiles", areaId + "/" + zoomCord + "x" + xCord + "x" + yCord + ".png"); + + try { + image = BitmapFactory.decodeFile(myFile.getAbsolutePath(), options); + } catch (Exception e) { + e.printStackTrace(); + return NO_TILE; + } + } + int zsteps = 1; + + if (zoom > this.maxZoom) { + zsteps = zoom - this.maxZoom; + int relation = (int) Math.pow(2, zsteps) ; + int size = (int) (TILE_SIZE / relation); + // we scale the map to keep the tiles sharp + scaleSize = (int) (TILE_SIZE * 2); + srcX = (int) size * (x % relation); + srcY = (int) size * (y % relation); + srcW = (int) size; + srcH = (int) size; + } + + if (image != null) { + Bitmap croppedBitmap = Bitmap.createBitmap(image , srcX , srcY, srcW, srcH); + Bitmap scaledBitmap = croppedBitmap; + if (zoom > maxZoom) { + // The last false is for filter anti-aliasing + scaledBitmap = Bitmap.createScaledBitmap (croppedBitmap, scaleSize, scaleSize, false); + } + + int width, height; + height = scaledBitmap.getHeight(); + width = scaledBitmap.getWidth(); + + // + + int[] pixels = new int[width * height]; + scaledBitmap.getPixels(pixels, 0, width, 0, 0, width, height); + + + Bitmap finalBitmap = paintTile(scaledBitmap, width, height, zoom, zsteps, minDate, maxDate); + + + + stream = new ByteArrayOutputStream(); + finalBitmap.compress(Bitmap.CompressFormat.PNG, 0, stream); + bitmapData = stream.toByteArray(); + return new Tile(TILE_SIZE, TILE_SIZE, bitmapData); + } else { + return NO_TILE; + } + } + + protected abstract Context getParentContext(); + + protected abstract Bitmap paintTile(Bitmap scaledBitmap, int width, int height, int zoom, int zsteps, int minDate, int maxDate); + + public void setUrlTemplate(String urlTemplate) { + this.urlTemplate = urlTemplate; + } + + public void setMaxZoom(int maxZoom) { + this.maxZoom = maxZoom; + } + + public void setAreaId(String areaId) { + this.areaId = areaId; + } + + public void setAlertType(String alertType) { + this.alertType = alertType; + } + + public void setIsConnected(boolean isConnected) { + this.isConnected = isConnected; + } + + public void setCoordinates(Coordinates coordinates) { + this.coordinates = coordinates; + } +} diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasUrlTile.java b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasUrlTile.java new file mode 100644 index 000000000..bac7b824f --- /dev/null +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasUrlTile.java @@ -0,0 +1,82 @@ +package com.airbnb.android.react.maps; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import com.google.android.gms.maps.model.TileOverlayOptions; + + +public class AirMapCanvasUrlTile extends AirMapCanvasFeature { + + public AirMapCanvasUrlTile(Context context){ super(context); } + + class AIRMapCanvasUrlTileProvider extends AirMapCanvasTileProvider { + + public AIRMapCanvasUrlTileProvider(int width, int height, String urlTemplate, int maxZoom, String areaId, boolean isConnected, String minDate, String maxDate, String alertType, Coordinates coordinates) { + super(width, height, urlTemplate, maxZoom, areaId, isConnected, minDate, maxDate, alertType, coordinates); + } + + protected Context getParentContext() { + return getContext(); + } + + protected Bitmap paintTile(Bitmap scaledBitmap, int width, int height, int zoom, int zsteps, int minDate, int maxDate) { + Bitmap finalBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + int red, green, blue, pixel, alpha; + int[] pixels = new int[width * height]; + scaledBitmap.getPixels(pixels, 0, width, 0, 0, width, height); + for (int i = 0; i < pixels.length; i++) { + pixel = pixels[i]; + + red = (pixel >> 16) & 0xFF; + green = (pixel >> 8) & 0xFF; + blue = pixel & 0xFF; + + if (red > 255) red = 255; + if (green > 255) green = 255; + if (blue > 255) blue = 255; + + int day; + if (this.alertType != null && this.alertType.equals("viirs")) { + day = blue; + } else { + day = red * 255 + green; + } + + if(this.alertType != null && this.alertType.equals("viirs")) { + if (day > 0 && day >= minDate && day <= maxDate) { + red = 244; + green = 66; + blue = 66; + alpha = 255; + } else { + alpha = 0; + } + } else if (day > 0 && day >= minDate && day <= maxDate) { + red = 220; + green = 102; + blue = 153; + alpha = 255; + } else { + alpha = 0; + } + + pixels[i] = Color.argb(alpha, red, green, blue); + } + finalBitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return finalBitmap; + } + + + } + + + protected TileOverlayOptions createTileOverlayOptions() { + TileOverlayOptions options = new TileOverlayOptions(); + options.zIndex(zIndex); + this.tileProvider = new AIRMapCanvasUrlTileProvider(256, 256, this.urlTemplate, this.maxZoom, this.areaId, this.isConnected, this.minDate, this.maxDate, this.alertType, null); + options.tileProvider(this.tileProvider); + return options; + } + +} diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasUrlTileManager.java b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasUrlTileManager.java new file mode 100644 index 000000000..b15ccbb37 --- /dev/null +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapCanvasUrlTileManager.java @@ -0,0 +1,79 @@ +package com.airbnb.android.react.maps; + +import android.content.Context; +import android.os.Build; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.WindowManager; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewGroupManager; +import com.facebook.react.uimanager.annotations.ReactProp; + +public class AirMapCanvasUrlTileManager extends ViewGroupManager { + private DisplayMetrics metrics; + + public AirMapCanvasUrlTileManager(ReactApplicationContext reactContext) { + super(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + metrics = new DisplayMetrics(); + ((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay() + .getRealMetrics(metrics); + } else { + metrics = reactContext.getResources().getDisplayMetrics(); + } + } + + @Override + public String getName() { + return "AIRMapCanvasUrlTile"; + } + + @Override + public AirMapCanvasUrlTile createViewInstance(ThemedReactContext context) { + return new AirMapCanvasUrlTile(context); + } + + @ReactProp(name = "urlTemplate") + public void setUrlTemplate(AirMapCanvasUrlTile view, String urlTemplate) { + view.setUrlTemplate(urlTemplate); + } + + @ReactProp(name = "maxZoom", defaultInt = 12) + public void setMaxZoom(AirMapCanvasUrlTile view, int maxZoom) { + view.setMaxZoom(maxZoom); + } + + @ReactProp(name = "alertType") + public void setAlertType(AirMapCanvasUrlTile view, String alertType) { + view.setAlertType(alertType); + } + + @ReactProp(name = "areaId") + public void setAreaId(AirMapCanvasUrlTile view, String areaId) { + view.setAreaId(areaId); + } + + @ReactProp(name = "isConnected", defaultBoolean = true) + public void setIsConnected(AirMapCanvasUrlTile view, boolean isConnected) { + view.setIsConnected(isConnected); + } + + @ReactProp(name = "minDate") + public void setMinDate(AirMapCanvasUrlTile view, String minDate) { + view.setMinDate(minDate); + } + + @ReactProp(name = "maxDate") + public void setMaxDate(AirMapCanvasUrlTile view, String maxDate) { + view.setMaxDate(maxDate); + } + + @ReactProp(name = "zIndex", defaultFloat = -1.0f) + public void setZIndex(AirMapCanvasUrlTile view, float zIndex) { + view.setZIndex(zIndex); + } + +} diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapView.java b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapView.java index 3225bf1ff..a4610efff 100644 --- a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapView.java +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapView.java @@ -12,6 +12,7 @@ import android.os.Handler; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.MotionEventCompat; +import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; @@ -462,6 +463,7 @@ public void setHandlePanDrag(boolean handlePanDrag) { public void addFeature(View child, int index) { // Our desired API is to pass up annotations/overlays as children to the mapview component. // This is where we intercept them and do the appropriate underlying mapview action. + if (child instanceof AirMapMarker) { AirMapMarker annotation = (AirMapMarker) child; annotation.addToMap(map); @@ -488,6 +490,14 @@ public void addFeature(View child, int index) { AirMapUrlTile urlTileView = (AirMapUrlTile) child; urlTileView.addToMap(map); features.add(index, urlTileView); + } else if (child instanceof AirMapCanvasUrlTile) { + AirMapCanvasUrlTile canvasUrlTileView = (AirMapCanvasUrlTile) child; + canvasUrlTileView.addToMap(map); + features.add(index, canvasUrlTileView); + } else if (child instanceof AirMapCanvasInteractionUrlTile) { + AirMapCanvasInteractionUrlTile canvasInteractionUrlTileView = (AirMapCanvasInteractionUrlTile) child; + canvasInteractionUrlTileView.addToMap(map); + features.add(index, canvasInteractionUrlTileView); } else { ViewGroup children = (ViewGroup) child; for (int i = 0; i < children.getChildCount(); i++) { diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/Coordinates.java b/lib/android/src/main/java/com/airbnb/android/react/maps/Coordinates.java new file mode 100644 index 000000000..5de7247d7 --- /dev/null +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/Coordinates.java @@ -0,0 +1,22 @@ +package com.airbnb.android.react.maps; + +/** + * Created by joseangel on 25/05/2017. + */ + +public class Coordinates { + private int[] tile; + private double[] precision; + + public Coordinates(int[] tile, double[] precision) { + this.tile = tile; + this.precision = precision; + } + + public int[] getTile() { + return tile; + } + public double[] getPrecision() { + return precision; + } +} diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/MapsPackage.java b/lib/android/src/main/java/com/airbnb/android/react/maps/MapsPackage.java index e76f63aa2..5284e1581 100644 --- a/lib/android/src/main/java/com/airbnb/android/react/maps/MapsPackage.java +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/MapsPackage.java @@ -39,6 +39,8 @@ public List createViewManagers(ReactApplicationContext reactContext AirMapManager mapManager = new AirMapManager(reactContext); AirMapLiteManager mapLiteManager = new AirMapLiteManager(reactContext); AirMapUrlTileManager tileManager = new AirMapUrlTileManager(reactContext); + AirMapCanvasUrlTileManager canvasTileManager = new AirMapCanvasUrlTileManager(reactContext); + AirMapCanvasInteractionUrlTileManager canvasInteractionTileManager = new AirMapCanvasInteractionUrlTileManager(reactContext); return Arrays.asList( calloutManager, @@ -48,6 +50,8 @@ public List createViewManagers(ReactApplicationContext reactContext circleManager, mapManager, mapLiteManager, - tileManager); + tileManager, + canvasTileManager, + canvasInteractionTileManager); } } diff --git a/lib/components/MapCanvasInteractionUrlTile.js b/lib/components/MapCanvasInteractionUrlTile.js new file mode 100644 index 000000000..cafddfa06 --- /dev/null +++ b/lib/components/MapCanvasInteractionUrlTile.js @@ -0,0 +1,90 @@ +import React, { PropTypes } from 'react'; + +import { + View, +} from 'react-native'; + +import decorateMapComponent, { + USES_DEFAULT_IMPLEMENTATION, + SUPPORTED, +} from './decorateMapComponent'; + +const viewConfig = { + uiViewClassName: 'AIRCanvasInteractionUrlTile', +}; + +const propTypes = { + ...View.propTypes, + + /** + * The url template of the tile server. The patterns {x} {y} {z} will be replaced at runtime + * For example, http://c.tile.openstreetmap.org/{z}/{x}/{y}.png + */ + urlTemplate: PropTypes.string.isRequired, + + /** + * The order in which this tile overlay is drawn with respect to other overlays. An overlay + * with a larger z-index is drawn over overlays with smaller z-indices. The order of overlays + * with the same z-index is arbitrary. The default zIndex is -1. + * + * @platform android + */ + zIndex: PropTypes.number, + /** + * Flag to use the offline tiles instead of the url version + */ + isConnected: PropTypes.bool, + /** + * Area id to get the tiles from the corrent folder + */ + areaId: PropTypes.string, + /** + * Min date to get tiles + */ + minDate: PropTypes.string, + /** + * Max date to get tiles + */ + maxDate: PropTypes.string, + /** + * Max zoom when the tiles have data + */ + maxZoom: PropTypes.number, + /** + * Max zoom when the tiles have data + */ + coordinates: PropTypes.shape({ + tile: PropTypes.arrayOf(React.PropTypes.number), + precision: PropTypes.arrayOf(React.PropTypes.number), + }), +}; + +const defaultProps = { + maxZoom: 12, + isConnected: true, +}; + +class CanvasInteractionUrlTile extends React.Component { + render() { + const AIRMapCanvasInteractionUrlTile = this.getAirComponent(); + return ( + + ); + } +} + +CanvasInteractionUrlTile.viewConfig = viewConfig; +CanvasInteractionUrlTile.propTypes = propTypes; +CanvasInteractionUrlTile.defaultProps = defaultProps; + +module.exports = decorateMapComponent(CanvasInteractionUrlTile, { + componentType: 'CanvasInteractionUrlTile', + providers: { + google: { + ios: SUPPORTED, + android: USES_DEFAULT_IMPLEMENTATION, + }, + }, +}); diff --git a/lib/components/MapCanvasUrlTile.js b/lib/components/MapCanvasUrlTile.js new file mode 100644 index 000000000..49a5509c8 --- /dev/null +++ b/lib/components/MapCanvasUrlTile.js @@ -0,0 +1,87 @@ +import React, { PropTypes } from 'react'; + +import { + View, +} from 'react-native'; + +import decorateMapComponent, { + USES_DEFAULT_IMPLEMENTATION, + SUPPORTED, +} from './decorateMapComponent'; + +const viewConfig = { + uiViewClassName: 'AIRCanvasUrlTile', +}; + +const propTypes = { + ...View.propTypes, + + /** + * The url template of the tile server. The patterns {x} {y} {z} will be replaced at runtime + * For example, http://c.tile.openstreetmap.org/{z}/{x}/{y}.png + */ + urlTemplate: PropTypes.string.isRequired, + + /** + * The order in which this tile overlay is drawn with respect to other overlays. An overlay + * with a larger z-index is drawn over overlays with smaller z-indices. The order of overlays + * with the same z-index is arbitrary. The default zIndex is -1. + * + * @platform android + */ + zIndex: PropTypes.number, + /** + * Flag to use the offline tiles instead of the url version + */ + isConnected: PropTypes.bool, + /** + * Area id to get the tiles from the corrent folder + */ + areaId: PropTypes.string, + /** + * Alert type supported umd_as_it_happens OR viirs + */ + alertType: PropTypes.string, + /** + * Min date to get tiles + */ + minDate: PropTypes.string, + /** + * Max date to get tiles + */ + maxDate: PropTypes.string, + /** + * Max zoom when the tiles have data + */ + maxZoom: PropTypes.number, +}; + +const defaultProps = { + maxZoom: 12, + isConnected: true, +}; + +class CanvasUrlTile extends React.Component { + render() { + const AIRMapCanvasUrlTile = this.getAirComponent(); + return ( + + ); + } +} + +CanvasUrlTile.viewConfig = viewConfig; +CanvasUrlTile.propTypes = propTypes; +CanvasUrlTile.defaultProps = defaultProps; + +module.exports = decorateMapComponent(CanvasUrlTile, { + componentType: 'CanvasUrlTile', + providers: { + google: { + ios: SUPPORTED, + android: USES_DEFAULT_IMPLEMENTATION, + }, + }, +}); diff --git a/lib/components/MapView.js b/lib/components/MapView.js index 5eb2284a5..1b416687b 100644 --- a/lib/components/MapView.js +++ b/lib/components/MapView.js @@ -15,6 +15,8 @@ import MapPolygon from './MapPolygon'; import MapCircle from './MapCircle'; import MapCallout from './MapCallout'; import MapUrlTile from './MapUrlTile'; +import MapCanvasUrlTile from './MapCanvasUrlTile'; +import MapCanvasInteractionUrlTile from './MapCanvasInteractionUrlTile'; import AnimatedRegion from './AnimatedRegion'; import { contextTypes as childContextTypes, @@ -680,6 +682,8 @@ MapView.Polyline = MapPolyline; MapView.Polygon = MapPolygon; MapView.Circle = MapCircle; MapView.UrlTile = MapUrlTile; +MapView.CanvasUrlTile = MapCanvasUrlTile; +MapView.CanvasInteractionUrlTile = MapCanvasInteractionUrlTile; MapView.Callout = MapCallout; Object.assign(MapView, ProviderConstants); MapView.ProviderPropType = PropTypes.oneOf(Object.values(ProviderConstants)); diff --git a/package.json b/package.json index 30ffc43e8..772551c11 100644 --- a/package.json +++ b/package.json @@ -55,5 +55,9 @@ "android": { "sourceDir": "./lib/android" } + }, + "dependencies": { + "@mapbox/geo-viewport": "^0.2.2", + "@mapbox/tilebelt": "Vizzuality/tilebelt" } }