react-native-maps 0.29.4 → 0.31.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -5
- package/index.d.ts +21 -5
- package/index.js +1 -3
- package/lib/android/build.gradle +3 -1
- package/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapTileProvider.java +515 -0
- package/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapTileWorker.java +122 -0
- package/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapUrlTile.java +124 -55
- package/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapUrlTileManager.java +34 -0
- package/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapView.java +19 -4
- package/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapWMSTile.java +31 -87
- package/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapWMSTileManager.java +22 -2
- package/lib/components/AnimatedRegion.js +67 -101
- package/lib/components/Geojson.js +67 -40
- package/lib/components/MapCallout.js +3 -5
- package/lib/components/MapCalloutSubview.js +3 -5
- package/lib/components/MapCircle.js +6 -6
- package/lib/components/MapHeatmap.js +5 -7
- package/lib/components/MapLocalTile.js +2 -6
- package/lib/components/MapMarker.js +9 -9
- package/lib/components/MapOverlay.js +3 -2
- package/lib/components/MapPolygon.js +6 -6
- package/lib/components/MapPolyline.js +6 -7
- package/lib/components/MapUrlTile.js +56 -8
- package/lib/components/MapView.js +16 -16
- package/lib/components/MapWMSTile.js +51 -8
- package/lib/components/decorateMapComponent.js +1 -1
- package/lib/ios/AirGoogleMaps/AIRGoogleMapMarker.h +1 -0
- package/lib/ios/AirGoogleMaps/AIRGoogleMapMarker.m +8 -0
- package/lib/ios/AirGoogleMaps/AIRGoogleMapMarkerManager.m +1 -0
- package/lib/ios/AirMaps/AIRMapManager.m +21 -3
- package/lib/ios/AirMaps/AIRMapMarkerManager.m +1 -0
- package/lib/ios/AirMaps/AIRMapPolyline.h +1 -0
- package/lib/ios/AirMaps/AIRMapPolyline.m +14 -1
- package/lib/ios/AirMaps/AIRMapPolylineManager.m +1 -0
- package/lib/ios/AirMaps/AIRMapUrlTile.h +22 -4
- package/lib/ios/AirMaps/AIRMapUrlTile.m +124 -34
- package/lib/ios/AirMaps/AIRMapUrlTileCachedOverlay.h +17 -0
- package/lib/ios/AirMaps/AIRMapUrlTileCachedOverlay.m +235 -0
- package/lib/ios/AirMaps/AIRMapUrlTileManager.m +6 -1
- package/lib/ios/AirMaps/AIRMapWMSTile.h +10 -16
- package/lib/ios/AirMaps/AIRMapWMSTile.m +68 -101
- package/lib/ios/AirMaps/AIRMapWMSTileManager.m +6 -1
- package/lib/ios/AirMaps.xcodeproj/project.pbxproj +7 -1
- package/package.json +44 -6
- package/react-native-google-maps.podspec +3 -3
- package/react-native-maps.podspec +1 -1
- package/.eslintrc +0 -3
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -111
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -32
- package/.github/ISSUE_TEMPLATE.md +0 -5
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -53
- package/.github/stale.yml +0 -17
- package/.github/workflows/base.yml +0 -21
- package/.prettierrc +0 -4
- package/CHANGELOG.md +0 -761
- package/docs/callout.md +0 -38
- package/docs/circle.md +0 -28
- package/docs/examples-setup.md +0 -17
- package/docs/geojson.md +0 -50
- package/docs/heatmap.md +0 -32
- package/docs/installation.md +0 -347
- package/docs/mapview.md +0 -252
- package/docs/marker.md +0 -84
- package/docs/overlay.md +0 -27
- package/docs/polygon.md +0 -34
- package/docs/polyline.md +0 -65
- package/enable-google-maps +0 -15
package/README.md
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
React Native Map components for iOS + Android
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
## Contributing
|
|
7
|
+
This project is being maintained by a small group of people, and any help with issues and pull requests are always appreciated. If you are able and willing to contribute, please read the [guidelines](./CONTRIBUTING.md).
|
|
8
|
+
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
11
|
See [Installation Instructions](docs/installation.md).
|
|
@@ -245,7 +245,7 @@ render() {
|
|
|
245
245
|
region={this.state.region}
|
|
246
246
|
/>
|
|
247
247
|
<OverlayComponent
|
|
248
|
-
style={{position:
|
|
248
|
+
style={{position: "absolute", bottom: 50}}
|
|
249
249
|
/>
|
|
250
250
|
);
|
|
251
251
|
}
|
|
@@ -482,6 +482,7 @@ componentWillReceiveProps(nextProps) {
|
|
|
482
482
|
} else {
|
|
483
483
|
this.state.coordinate.timing({
|
|
484
484
|
...nextProps.coordinate,
|
|
485
|
+
useNativeDriver: true, // defaults to false if not passed explicitly
|
|
485
486
|
duration
|
|
486
487
|
}).start();
|
|
487
488
|
}
|
|
@@ -603,7 +604,7 @@ Good:
|
|
|
603
604
|
</View>
|
|
604
605
|
```
|
|
605
606
|
|
|
606
|
-
|
|
607
|
+
#### Children Components Not Re-Rendering
|
|
607
608
|
Components that aren't declared by this library (Ex: Markers, Polyline) must not be children of the MapView component due to MapView's unique rendering methodology. Have your custom components / views outside the MapView component and position absolute to ensure they only re-render as needed.
|
|
608
609
|
Example:
|
|
609
610
|
Bad:
|
|
@@ -627,6 +628,12 @@ Good:
|
|
|
627
628
|
|
|
628
629
|
Source: https://github.com/react-native-maps/react-native-maps/issues/1901
|
|
629
630
|
|
|
631
|
+
#### Crashing with EXC_BAD_ACCESS on iOS when switching apps
|
|
632
|
+
|
|
633
|
+
`<MapView>` using Apple Maps in `mapType: "standard"` will sometimes crash when you background the app or switch into another app. This is only an issue in XCode using Metal API Validation, and won't happen in production. To eliminate this problem even while debugging in XCode, go to `Edit Scheme... -> Run (Debug) -> Diagnostics` and uncheck `Metal -> API Validation`. (h/t [@Simon-TechForm](https://github.com/Simon-TechForm)).
|
|
634
|
+
|
|
635
|
+
Source: https://github.com/react-native-maps/react-native-maps/issues/3957#issuecomment-924161121
|
|
636
|
+
|
|
630
637
|
License
|
|
631
638
|
--------
|
|
632
639
|
|
package/index.d.ts
CHANGED
|
@@ -80,6 +80,7 @@ declare module 'react-native-maps' {
|
|
|
80
80
|
easing?: (value: number) => number;
|
|
81
81
|
duration?: number;
|
|
82
82
|
delay?: number;
|
|
83
|
+
useNativeDriver: boolean;
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
interface AnimatedRegionSpringConfig
|
|
@@ -96,6 +97,7 @@ declare module 'react-native-maps' {
|
|
|
96
97
|
stiffness?: number;
|
|
97
98
|
mass?: number;
|
|
98
99
|
damping?: number;
|
|
100
|
+
useNativeDriver: boolean;
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
export class AnimatedRegion extends RNAnimated.AnimatedWithChildren {
|
|
@@ -300,6 +302,7 @@ declare module 'react-native-maps' {
|
|
|
300
302
|
}
|
|
301
303
|
|
|
302
304
|
export default class MapView extends React.Component<MapViewProps, any> {
|
|
305
|
+
private __lastRegion?: Region;
|
|
303
306
|
getCamera(): Promise<Camera>;
|
|
304
307
|
setCamera(camera: Partial<Camera>): void;
|
|
305
308
|
animateCamera(camera: Partial<Camera>, opts?: { duration?: number }): void;
|
|
@@ -356,6 +359,7 @@ declare module 'react-native-maps' {
|
|
|
356
359
|
calloutAnchor?: Point;
|
|
357
360
|
flat?: boolean;
|
|
358
361
|
draggable?: boolean;
|
|
362
|
+
tappable?: boolean;
|
|
359
363
|
tracksViewChanges?: boolean;
|
|
360
364
|
tracksInfoWindowChanges?: boolean;
|
|
361
365
|
stopPropagation?: boolean;
|
|
@@ -504,10 +508,16 @@ declare module 'react-native-maps' {
|
|
|
504
508
|
urlTemplate: string;
|
|
505
509
|
minimumZ?: number;
|
|
506
510
|
maximumZ?: number;
|
|
511
|
+
maximumNativeZ?: number;
|
|
507
512
|
zIndex?: number;
|
|
508
513
|
tileSize?: number;
|
|
514
|
+
doubleTileSize?: boolean;
|
|
509
515
|
shouldReplaceMapContent?: boolean;
|
|
510
516
|
flipY?: boolean;
|
|
517
|
+
tileCachePath?: string;
|
|
518
|
+
tileCacheMaxAge?: number;
|
|
519
|
+
offlineMode?: boolean;
|
|
520
|
+
opacity?: number;
|
|
511
521
|
}
|
|
512
522
|
|
|
513
523
|
export class UrlTile extends React.Component<MapUrlTileProps, any> {}
|
|
@@ -527,12 +537,16 @@ declare module 'react-native-maps' {
|
|
|
527
537
|
|
|
528
538
|
export interface MapWMSTileProps extends ViewProperties {
|
|
529
539
|
urlTemplate: string;
|
|
530
|
-
maximumZ?: number;
|
|
531
540
|
minimumZ?: number;
|
|
532
|
-
|
|
533
|
-
|
|
541
|
+
maximumZ?: number;
|
|
542
|
+
maximumNativeZ?: number;
|
|
534
543
|
zIndex?: number;
|
|
544
|
+
tileSize?: number;
|
|
535
545
|
shouldReplaceMapContent?: boolean;
|
|
546
|
+
tileCachePath?: string;
|
|
547
|
+
tileCacheMaxAge?: number;
|
|
548
|
+
offlineMode?: boolean;
|
|
549
|
+
opacity?: number;
|
|
536
550
|
}
|
|
537
551
|
|
|
538
552
|
export class WMSTile extends React.Component<MapWMSTileProps, any> {}
|
|
@@ -584,7 +598,7 @@ declare module 'react-native-maps' {
|
|
|
584
598
|
import GeoJSON from 'geojson';
|
|
585
599
|
|
|
586
600
|
export interface GeojsonProps {
|
|
587
|
-
geojson: GeoJSON.
|
|
601
|
+
geojson: GeoJSON.FeatureCollection;
|
|
588
602
|
strokeColor?: string;
|
|
589
603
|
fillColor?: string;
|
|
590
604
|
strokeWidth?: number;
|
|
@@ -594,8 +608,10 @@ declare module 'react-native-maps' {
|
|
|
594
608
|
lineJoin?: 'miter' | 'round' | 'bevel';
|
|
595
609
|
miterLimit?: number;
|
|
596
610
|
zIndex?: number;
|
|
611
|
+
tappable?: boolean;
|
|
612
|
+
title?: string;
|
|
597
613
|
onPress?: (event: MapEvent) => void;
|
|
598
|
-
markerComponent?: React.ReactNode
|
|
614
|
+
markerComponent?: React.ReactNode;
|
|
599
615
|
}
|
|
600
616
|
|
|
601
617
|
export class Geojson extends React.Component<GeojsonProps, any> {}
|
package/index.js
CHANGED
|
@@ -14,9 +14,7 @@ export { default as UrlTile } from './lib/components/MapUrlTile.js';
|
|
|
14
14
|
export { default as WMSTile } from './lib/components/MapWMSTile.js';
|
|
15
15
|
export { default as LocalTile } from './lib/components/MapLocalTile.js';
|
|
16
16
|
export { default as Callout } from './lib/components/MapCallout.js';
|
|
17
|
-
export {
|
|
18
|
-
default as CalloutSubview,
|
|
19
|
-
} from './lib/components/MapCalloutSubview.js';
|
|
17
|
+
export { default as CalloutSubview } from './lib/components/MapCalloutSubview.js';
|
|
20
18
|
export { default as AnimatedRegion } from './lib/components/AnimatedRegion.js';
|
|
21
19
|
export { default as Geojson } from './lib/components/Geojson.js';
|
|
22
20
|
|
package/lib/android/build.gradle
CHANGED
|
@@ -11,12 +11,13 @@ android {
|
|
|
11
11
|
compileSdkVersion safeExtGet('compileSdkVersion', 28)
|
|
12
12
|
|
|
13
13
|
defaultConfig {
|
|
14
|
-
minSdkVersion safeExtGet('minSdkVersion',
|
|
14
|
+
minSdkVersion safeExtGet('minSdkVersion', 21)
|
|
15
15
|
targetSdkVersion safeExtGet('targetSdkVersion', 27)
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
dependencies {
|
|
20
|
+
def work_version = safeExtGet('targetSdkVersion', 27) < 31 ? "2.5.0" : "2.7.1"
|
|
20
21
|
def supportLibVersion = safeExtGet('supportLibVersion', '28.0.0')
|
|
21
22
|
def supportLibMajorVersion = supportLibVersion.split('\\.')[0] as int
|
|
22
23
|
def appCompatLibName = (supportLibMajorVersion < 20) ? "androidx.appcompat:appcompat" : "com.android.support:appcompat-v7"
|
|
@@ -28,4 +29,5 @@ dependencies {
|
|
|
28
29
|
implementation "com.google.android.gms:play-services-maps:${safeExtGet('playServicesVersion', '17.0.0')}"
|
|
29
30
|
implementation "com.google.android.gms:play-services-location:17.0.0"
|
|
30
31
|
implementation 'com.google.maps.android:android-maps-utils:0.5'
|
|
32
|
+
implementation "androidx.work:work-runtime:$work_version"
|
|
31
33
|
}
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
package com.airbnb.android.react.maps;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
|
|
5
|
+
import android.util.Log;
|
|
6
|
+
import java.util.concurrent.TimeUnit;
|
|
7
|
+
import java.util.concurrent.Future;
|
|
8
|
+
import java.util.List;
|
|
9
|
+
|
|
10
|
+
import android.graphics.Bitmap;
|
|
11
|
+
import android.graphics.BitmapFactory;
|
|
12
|
+
import android.graphics.Canvas;
|
|
13
|
+
import android.graphics.Color;
|
|
14
|
+
import android.graphics.Paint;
|
|
15
|
+
import android.graphics.Rect;
|
|
16
|
+
|
|
17
|
+
import androidx.annotation.NonNull;
|
|
18
|
+
import androidx.work.OneTimeWorkRequest;
|
|
19
|
+
import androidx.work.WorkManager;
|
|
20
|
+
import androidx.work.WorkRequest;
|
|
21
|
+
import androidx.work.Worker;
|
|
22
|
+
import androidx.work.WorkerParameters;
|
|
23
|
+
import androidx.work.Data;
|
|
24
|
+
import androidx.work.Constraints;
|
|
25
|
+
import androidx.work.NetworkType;
|
|
26
|
+
import androidx.work.ExistingWorkPolicy;
|
|
27
|
+
import androidx.work.Operation;
|
|
28
|
+
import androidx.work.WorkInfo;
|
|
29
|
+
|
|
30
|
+
import com.google.android.gms.maps.GoogleMap;
|
|
31
|
+
import com.google.android.gms.maps.model.Tile;
|
|
32
|
+
import com.google.android.gms.maps.model.TileOverlay;
|
|
33
|
+
import com.google.android.gms.maps.model.TileOverlayOptions;
|
|
34
|
+
import com.google.android.gms.maps.model.TileProvider;
|
|
35
|
+
import com.google.android.gms.maps.model.UrlTileProvider;
|
|
36
|
+
|
|
37
|
+
import java.lang.System;
|
|
38
|
+
|
|
39
|
+
import java.io.ByteArrayOutputStream;
|
|
40
|
+
import java.io.File;
|
|
41
|
+
import java.io.FileInputStream;
|
|
42
|
+
import java.io.FileOutputStream;
|
|
43
|
+
import java.io.InputStream;
|
|
44
|
+
import java.io.OutputStream;
|
|
45
|
+
import java.io.IOException;
|
|
46
|
+
|
|
47
|
+
import java.net.MalformedURLException;
|
|
48
|
+
import java.net.URL;
|
|
49
|
+
import java.net.URLConnection;
|
|
50
|
+
|
|
51
|
+
public class AirMapTileProvider implements TileProvider {
|
|
52
|
+
|
|
53
|
+
class AIRMapUrlTileProvider extends UrlTileProvider {
|
|
54
|
+
private String urlTemplate;
|
|
55
|
+
|
|
56
|
+
public AIRMapUrlTileProvider(int width, int height, String urlTemplate) {
|
|
57
|
+
super(width, height);
|
|
58
|
+
this.urlTemplate = urlTemplate;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Override
|
|
62
|
+
public URL getTileUrl(int x, int y, int zoom) {
|
|
63
|
+
|
|
64
|
+
if (AirMapTileProvider.this.flipY == true) {
|
|
65
|
+
y = (1 << zoom) - y - 1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
String s = this.urlTemplate
|
|
69
|
+
.replace("{x}", Integer.toString(x))
|
|
70
|
+
.replace("{y}", Integer.toString(y))
|
|
71
|
+
.replace("{z}", Integer.toString(zoom));
|
|
72
|
+
URL url = null;
|
|
73
|
+
|
|
74
|
+
if(AirMapTileProvider.this.maximumZ > 0 && zoom > AirMapTileProvider.this.maximumZ) {
|
|
75
|
+
return url;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if(AirMapTileProvider.this.minimumZ > 0 && zoom < AirMapTileProvider.this.minimumZ) {
|
|
79
|
+
return url;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
url = new URL(s);
|
|
84
|
+
} catch (MalformedURLException e) {
|
|
85
|
+
throw new AssertionError(e);
|
|
86
|
+
}
|
|
87
|
+
return url;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public void setUrlTemplate(String urlTemplate) {
|
|
91
|
+
this.urlTemplate = urlTemplate;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
protected static final int BUFFER_SIZE = 16 * 1024;
|
|
96
|
+
protected static final int TARGET_TILE_SIZE = 512;
|
|
97
|
+
protected UrlTileProvider tileProvider;
|
|
98
|
+
protected String urlTemplate;
|
|
99
|
+
protected int tileSize;
|
|
100
|
+
protected boolean doubleTileSize;
|
|
101
|
+
protected int maximumZ;
|
|
102
|
+
protected int maximumNativeZ;
|
|
103
|
+
protected int minimumZ;
|
|
104
|
+
protected boolean flipY;
|
|
105
|
+
protected String tileCachePath;
|
|
106
|
+
protected int tileCacheMaxAge;
|
|
107
|
+
protected boolean offlineMode;
|
|
108
|
+
protected Context context;
|
|
109
|
+
protected boolean customMode;
|
|
110
|
+
|
|
111
|
+
public AirMapTileProvider(int tileSizet, boolean doubleTileSize, String urlTemplate,
|
|
112
|
+
int maximumZ, int maximumNativeZ, int minimumZ, boolean flipY, String tileCachePath,
|
|
113
|
+
int tileCacheMaxAge, boolean offlineMode, Context context, boolean customMode) {
|
|
114
|
+
this.tileProvider = new AIRMapUrlTileProvider(tileSizet, tileSizet, urlTemplate);
|
|
115
|
+
|
|
116
|
+
this.tileSize = tileSizet;
|
|
117
|
+
this.doubleTileSize = doubleTileSize;
|
|
118
|
+
this.urlTemplate = urlTemplate;
|
|
119
|
+
this.maximumZ = maximumZ;
|
|
120
|
+
this.maximumNativeZ = maximumNativeZ;
|
|
121
|
+
this.minimumZ = minimumZ;
|
|
122
|
+
this.flipY = flipY;
|
|
123
|
+
this.tileCachePath = tileCachePath;
|
|
124
|
+
this.tileCacheMaxAge = tileCacheMaxAge;
|
|
125
|
+
this.offlineMode = offlineMode;
|
|
126
|
+
this.context = context;
|
|
127
|
+
this.customMode = customMode;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@Override
|
|
131
|
+
public Tile getTile(int x, int y, int zoom) {
|
|
132
|
+
if (!this.customMode) return this.tileProvider.getTile(x, y, zoom);
|
|
133
|
+
|
|
134
|
+
byte[] image = null;
|
|
135
|
+
int maximumZ = this.maximumZ > 0 ? this.maximumZ : Integer.MAX_VALUE;
|
|
136
|
+
|
|
137
|
+
if (this.tileSize == 256 && this.doubleTileSize && zoom + 1 <= this.maximumNativeZ && zoom + 1 <= maximumZ) {
|
|
138
|
+
Log.d("urlTile", "pullTilesFromHigherZoom");
|
|
139
|
+
image = pullTilesFromHigherZoom(x, y, zoom);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (zoom > this.maximumNativeZ) {
|
|
143
|
+
Log.d("urlTile", "scaleLowerZoomTile");
|
|
144
|
+
image = scaleLowerZoomTile(x, y, zoom, this.maximumNativeZ);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (image == null && zoom <= maximumZ) {
|
|
148
|
+
Log.d("urlTile", "getTileImage");
|
|
149
|
+
image = getTileImage(x, y, zoom);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (image == null && this.tileCachePath != null && this.offlineMode) {
|
|
153
|
+
Log.d("urlTile", "findLowerZoomTileForScaling");
|
|
154
|
+
int zoomLevelToStart = (zoom > this.maximumNativeZ) ? this.maximumNativeZ - 1 : zoom - 1;
|
|
155
|
+
int minimumZoomToSearch = this.minimumZ >= zoom - 3 ? this.minimumZ : zoom - 3;
|
|
156
|
+
for (int tryZoom = zoomLevelToStart; tryZoom >= minimumZoomToSearch; tryZoom--) {
|
|
157
|
+
image = scaleLowerZoomTile(x, y, zoom, tryZoom);
|
|
158
|
+
if (image != null) {
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return image == null ? null : new Tile(this.tileSize, this.tileSize, image);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
byte[] getTileImage(int x, int y, int zoom) {
|
|
168
|
+
byte[] image = null;
|
|
169
|
+
boolean fallbackOnSyncFetch = false;
|
|
170
|
+
|
|
171
|
+
if (this.tileCachePath != null) {
|
|
172
|
+
image = readTileImage(x, y, zoom);
|
|
173
|
+
if (image != null) {
|
|
174
|
+
Log.d("urlTile: tile cache HIT for ", Integer.toString(zoom) +
|
|
175
|
+
"/" + Integer.toString(x) + "/" + Integer.toString(y));
|
|
176
|
+
} else {
|
|
177
|
+
Log.d("urlTile: tile cache MISS for ", Integer.toString(zoom) +
|
|
178
|
+
"/" + Integer.toString(x) + "/" + Integer.toString(y));
|
|
179
|
+
}
|
|
180
|
+
if (image != null && !this.offlineMode) {
|
|
181
|
+
checkForRefresh(x, y, zoom);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (image == null && !this.offlineMode && this.tileCachePath != null) {
|
|
186
|
+
String fileName = getTileFilename(x, y, zoom);
|
|
187
|
+
Constraints constraints = new Constraints.Builder()
|
|
188
|
+
.setRequiredNetworkType(NetworkType.CONNECTED)
|
|
189
|
+
.build();
|
|
190
|
+
OneTimeWorkRequest tileRefreshWorkRequest = new OneTimeWorkRequest.Builder(AirMapTileWorker.class)
|
|
191
|
+
.setConstraints(constraints)
|
|
192
|
+
.addTag(fileName)
|
|
193
|
+
.setInputData(
|
|
194
|
+
new Data.Builder()
|
|
195
|
+
.putString("url", getTileUrl(x, y, zoom).toString())
|
|
196
|
+
.putString("filename", fileName)
|
|
197
|
+
.putInt("maxAge", -1)
|
|
198
|
+
.build()
|
|
199
|
+
)
|
|
200
|
+
.build();
|
|
201
|
+
if (tileRefreshWorkRequest != null) {
|
|
202
|
+
WorkManager workManager = WorkManager.getInstance(this.context.getApplicationContext());
|
|
203
|
+
Operation fetchOperation = workManager
|
|
204
|
+
.enqueueUniqueWork(fileName, ExistingWorkPolicy.KEEP, tileRefreshWorkRequest);
|
|
205
|
+
Future<Operation.State.SUCCESS> operationFuture = fetchOperation.getResult();
|
|
206
|
+
try {
|
|
207
|
+
operationFuture.get(1L, TimeUnit.SECONDS);
|
|
208
|
+
Thread.sleep(500);
|
|
209
|
+
Future<List<WorkInfo>> fetchFuture = workManager.getWorkInfosByTag(fileName);
|
|
210
|
+
List<WorkInfo> workInfo = fetchFuture.get(1L, TimeUnit.SECONDS);
|
|
211
|
+
Log.d("urlTile: ", workInfo.get(0).toString());
|
|
212
|
+
if (this.tileCachePath != null) {
|
|
213
|
+
image = readTileImage(x, y, zoom);
|
|
214
|
+
if (image != null) {
|
|
215
|
+
Log.d("urlTile: tile cache fetch HIT for ", Integer.toString(zoom) +
|
|
216
|
+
"/" + Integer.toString(x) + "/" + Integer.toString(y));
|
|
217
|
+
} else {
|
|
218
|
+
Log.d("urlTile: tile cache fetch MISS for ", Integer.toString(zoom) +
|
|
219
|
+
"/" + Integer.toString(x) + "/" + Integer.toString(y));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} catch (Exception e) {
|
|
223
|
+
e.printStackTrace();
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
fallbackOnSyncFetch = true;
|
|
227
|
+
}
|
|
228
|
+
} else if (fallbackOnSyncFetch || (image == null && !this.offlineMode)) {
|
|
229
|
+
Log.d("urlTile", "Normal fetch");
|
|
230
|
+
image = fetchTile(x, y, zoom);
|
|
231
|
+
if (image == null) {
|
|
232
|
+
Log.d("urlTile: tile fetch TIMEOUT / FAIL for ", Integer.toString(zoom) +
|
|
233
|
+
"/" + Integer.toString(x) + "/" + Integer.toString(y));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return image;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
byte[] pullTilesFromHigherZoom(int x, int y, int zoom) {
|
|
241
|
+
byte[] data;
|
|
242
|
+
Bitmap image = getNewBitmap();
|
|
243
|
+
Canvas canvas = new Canvas(image);
|
|
244
|
+
Paint paint = new Paint();
|
|
245
|
+
|
|
246
|
+
x = x * 2;
|
|
247
|
+
y = y * 2;
|
|
248
|
+
byte[] leftTop = getTileImage(x, y, zoom + 1);
|
|
249
|
+
byte[] leftBottom = getTileImage(x, y + 1, zoom + 1);
|
|
250
|
+
byte[] rightTop = getTileImage(x + 1, y, zoom + 1);
|
|
251
|
+
byte[] rightBottom = getTileImage(x + 1, y + 1, zoom + 1);
|
|
252
|
+
|
|
253
|
+
if (leftTop == null || leftBottom == null || rightTop == null || rightBottom == null) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
Bitmap bitmap;
|
|
258
|
+
|
|
259
|
+
bitmap = BitmapFactory.decodeByteArray(leftTop, 0, leftTop.length);
|
|
260
|
+
canvas.drawBitmap(bitmap, 0, 0, paint);
|
|
261
|
+
bitmap.recycle();
|
|
262
|
+
|
|
263
|
+
bitmap = BitmapFactory.decodeByteArray(leftBottom, 0, leftBottom.length);
|
|
264
|
+
canvas.drawBitmap(bitmap, 0, 256, paint);
|
|
265
|
+
bitmap.recycle();
|
|
266
|
+
|
|
267
|
+
bitmap = BitmapFactory.decodeByteArray(rightTop, 0, rightTop.length);
|
|
268
|
+
canvas.drawBitmap(bitmap, 256, 0, paint);
|
|
269
|
+
bitmap.recycle();
|
|
270
|
+
|
|
271
|
+
bitmap = BitmapFactory.decodeByteArray(rightBottom, 0, rightBottom.length);
|
|
272
|
+
canvas.drawBitmap(bitmap, 256, 256, paint);
|
|
273
|
+
bitmap.recycle();
|
|
274
|
+
|
|
275
|
+
data = bitmapToByteArray(image);
|
|
276
|
+
image.recycle();
|
|
277
|
+
return data;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
Bitmap getNewBitmap() {
|
|
281
|
+
Bitmap image = Bitmap.createBitmap(TARGET_TILE_SIZE, TARGET_TILE_SIZE, Bitmap.Config.ARGB_8888);
|
|
282
|
+
image.eraseColor(Color.TRANSPARENT);
|
|
283
|
+
return image;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
byte[] bitmapToByteArray(Bitmap bm) {
|
|
287
|
+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
288
|
+
bm.compress(Bitmap.CompressFormat.PNG, 100, bos);
|
|
289
|
+
|
|
290
|
+
byte[] data = bos.toByteArray();
|
|
291
|
+
try {
|
|
292
|
+
bos.close();
|
|
293
|
+
} catch (Exception e) {
|
|
294
|
+
e.printStackTrace();
|
|
295
|
+
}
|
|
296
|
+
return data;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
byte[] scaleLowerZoomTile(int x, int y, int zoom, int maximumZoom) {
|
|
300
|
+
int overZoomLevel = zoom - maximumZoom;
|
|
301
|
+
int zoomFactor = 1 << overZoomLevel;
|
|
302
|
+
|
|
303
|
+
int xParent = x >> overZoomLevel;
|
|
304
|
+
int yParent = y >> overZoomLevel;
|
|
305
|
+
int zoomParent = zoom - overZoomLevel;
|
|
306
|
+
|
|
307
|
+
int xOffset = x % zoomFactor;;
|
|
308
|
+
int yOffset = y % zoomFactor;
|
|
309
|
+
|
|
310
|
+
byte[] data;
|
|
311
|
+
Bitmap image = getNewBitmap();
|
|
312
|
+
Canvas canvas = new Canvas(image);
|
|
313
|
+
Paint paint = new Paint();
|
|
314
|
+
|
|
315
|
+
data = getTileImage(xParent, yParent, zoomParent);
|
|
316
|
+
if (data == null) return null;
|
|
317
|
+
|
|
318
|
+
Bitmap sourceImage;
|
|
319
|
+
sourceImage = BitmapFactory.decodeByteArray(data, 0, data.length);
|
|
320
|
+
|
|
321
|
+
int subTileSize = this.tileSize / zoomFactor;
|
|
322
|
+
Rect sourceRect = new Rect(xOffset * subTileSize, yOffset * subTileSize, xOffset * subTileSize + subTileSize , yOffset * subTileSize + subTileSize);
|
|
323
|
+
Rect targetRect = new Rect(0,0,TARGET_TILE_SIZE, TARGET_TILE_SIZE);
|
|
324
|
+
canvas.drawBitmap(sourceImage, sourceRect, targetRect, paint);
|
|
325
|
+
sourceImage.recycle();
|
|
326
|
+
|
|
327
|
+
data = bitmapToByteArray(image);
|
|
328
|
+
image.recycle();
|
|
329
|
+
return data;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
void checkForRefresh(int x, int y, int zoom) {
|
|
333
|
+
String fileName = getTileFilename(x, y, zoom);
|
|
334
|
+
File file = new File(fileName);
|
|
335
|
+
long lastModified = file.lastModified();
|
|
336
|
+
long now = System.currentTimeMillis();
|
|
337
|
+
|
|
338
|
+
if ((now - lastModified) / 1000 > this.tileCacheMaxAge) {
|
|
339
|
+
Log.d("urlTile", "Refreshing");
|
|
340
|
+
Constraints constraints = new Constraints.Builder()
|
|
341
|
+
.setRequiredNetworkType(NetworkType.CONNECTED)
|
|
342
|
+
.build();
|
|
343
|
+
OneTimeWorkRequest tileRefreshWorkRequest = new OneTimeWorkRequest.Builder(AirMapTileWorker.class)
|
|
344
|
+
.setConstraints(constraints)
|
|
345
|
+
.addTag(fileName)
|
|
346
|
+
.setInputData(
|
|
347
|
+
new Data.Builder()
|
|
348
|
+
.putString("url", getTileUrl(x, y, zoom).toString())
|
|
349
|
+
.putString("filename", fileName)
|
|
350
|
+
.putInt("maxAge", this.tileCacheMaxAge)
|
|
351
|
+
.build()
|
|
352
|
+
)
|
|
353
|
+
.build();
|
|
354
|
+
if (tileRefreshWorkRequest != null) {
|
|
355
|
+
WorkManager.getInstance(this.context.getApplicationContext())
|
|
356
|
+
.enqueueUniqueWork(fileName, ExistingWorkPolicy.KEEP, tileRefreshWorkRequest);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
byte[] fetchTile(int x, int y, int zoom) {
|
|
362
|
+
URL url = getTileUrl(x, y, zoom);
|
|
363
|
+
ByteArrayOutputStream buffer = null;
|
|
364
|
+
InputStream in = null;
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
URLConnection conn = url.openConnection();
|
|
368
|
+
in = conn.getInputStream();
|
|
369
|
+
buffer = new ByteArrayOutputStream();
|
|
370
|
+
|
|
371
|
+
int nRead;
|
|
372
|
+
byte[] data = new byte[BUFFER_SIZE];
|
|
373
|
+
|
|
374
|
+
while ((nRead = in.read(data, 0, BUFFER_SIZE)) != -1) {
|
|
375
|
+
buffer.write(data, 0, nRead);
|
|
376
|
+
}
|
|
377
|
+
buffer.flush();
|
|
378
|
+
|
|
379
|
+
return buffer.toByteArray();
|
|
380
|
+
} catch (IOException e) {
|
|
381
|
+
e.printStackTrace();
|
|
382
|
+
return null;
|
|
383
|
+
} catch (OutOfMemoryError e) {
|
|
384
|
+
e.printStackTrace();
|
|
385
|
+
return null;
|
|
386
|
+
} finally {
|
|
387
|
+
if (in != null) try { in.close(); } catch (Exception ignored) {}
|
|
388
|
+
if (buffer != null) try { buffer.close(); } catch (Exception ignored) {}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
byte[] readTileImage(int x, int y, int zoom) {
|
|
393
|
+
InputStream in = null;
|
|
394
|
+
ByteArrayOutputStream buffer = null;
|
|
395
|
+
String fileName = getTileFilename(x, y, zoom);
|
|
396
|
+
if (fileName == null) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
File file = new File(fileName);
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
in = new FileInputStream(file);
|
|
404
|
+
buffer = new ByteArrayOutputStream();
|
|
405
|
+
|
|
406
|
+
int nRead;
|
|
407
|
+
byte[] data = new byte[BUFFER_SIZE];
|
|
408
|
+
|
|
409
|
+
while ((nRead = in.read(data, 0, BUFFER_SIZE)) != -1) {
|
|
410
|
+
buffer.write(data, 0, nRead);
|
|
411
|
+
}
|
|
412
|
+
buffer.flush();
|
|
413
|
+
|
|
414
|
+
if (this.tileCacheMaxAge == 0) {
|
|
415
|
+
file.setLastModified(System.currentTimeMillis());
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return buffer.toByteArray();
|
|
419
|
+
} catch (IOException e) {
|
|
420
|
+
e.printStackTrace();
|
|
421
|
+
return null;
|
|
422
|
+
} catch (OutOfMemoryError e) {
|
|
423
|
+
e.printStackTrace();
|
|
424
|
+
return null;
|
|
425
|
+
} finally {
|
|
426
|
+
if (in != null) try { in.close(); } catch (Exception ignored) {}
|
|
427
|
+
if (buffer != null) try { buffer.close(); } catch (Exception ignored) {}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
boolean writeTileImage(byte[] image, int x, int y, int zoom) {
|
|
432
|
+
OutputStream out = null;
|
|
433
|
+
String fileName = getTileFilename(x, y, zoom);
|
|
434
|
+
if (fileName == null) {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
File file = new File(fileName);
|
|
440
|
+
file.getParentFile().mkdirs();
|
|
441
|
+
out = new FileOutputStream(file);
|
|
442
|
+
out.write(image);
|
|
443
|
+
|
|
444
|
+
return true;
|
|
445
|
+
} catch (IOException e) {
|
|
446
|
+
e.printStackTrace();
|
|
447
|
+
return false;
|
|
448
|
+
} catch (OutOfMemoryError e) {
|
|
449
|
+
e.printStackTrace();
|
|
450
|
+
return false;
|
|
451
|
+
} finally {
|
|
452
|
+
if (out != null) try { out.close(); } catch (Exception ignored) {}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
String getTileFilename(int x, int y, int zoom) {
|
|
457
|
+
if (this.tileCachePath == null) {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
String s = this.tileCachePath + '/' + Integer.toString(zoom) +
|
|
461
|
+
"/" + Integer.toString(x) + "/" + Integer.toString(y);
|
|
462
|
+
return s;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
protected URL getTileUrl(int x, int y, int zoom) {
|
|
466
|
+
return this.tileProvider.getTileUrl(x, y, zoom);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
public void setUrlTemplate(String urlTemplate) {
|
|
470
|
+
this.urlTemplate = urlTemplate;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
public void setTileSize(int tileSize) {
|
|
474
|
+
if (this.tileSize != tileSize) {
|
|
475
|
+
this.tileProvider = new AIRMapUrlTileProvider(tileSize, tileSize, urlTemplate);
|
|
476
|
+
}
|
|
477
|
+
this.tileSize = tileSize;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
public void setDoubleTileSize(boolean doubleTileSize) {
|
|
481
|
+
this.doubleTileSize = doubleTileSize;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
public void setMaximumZ(int maximumZ) {
|
|
485
|
+
this.maximumZ = maximumZ;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
public void setMaximumNativeZ(int maximumNativeZ) {
|
|
489
|
+
this.maximumNativeZ = maximumNativeZ;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
public void setMinimumZ(int minimumZ) {
|
|
493
|
+
this.minimumZ = minimumZ;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
public void setFlipY(boolean flipY) {
|
|
497
|
+
this.flipY = flipY;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
public void setTileCachePath(String tileCachePath) {
|
|
501
|
+
this.tileCachePath = tileCachePath;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
public void setTileCacheMaxAge(int tileCacheMaxAge) {
|
|
505
|
+
this.tileCacheMaxAge = tileCacheMaxAge;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
public void setOfflineMode(boolean offlineMode) {
|
|
509
|
+
this.offlineMode = offlineMode;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
public void setCustomMode() {
|
|
513
|
+
this.customMode = customMode;
|
|
514
|
+
}
|
|
515
|
+
}
|