Skip to content

Commit 35544ff

Browse files
committed
feat(android): add marker icon remote image support
feat(android): add marker font support
1 parent f4badbc commit 35544ff

File tree

4 files changed

+108
-4
lines changed

4 files changed

+108
-4
lines changed

android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package com.rngooglemapsplus
22

33
import android.graphics.Bitmap
4+
import android.graphics.BitmapFactory
45
import android.graphics.Canvas
6+
import android.graphics.Typeface
57
import android.util.Base64
68
import android.util.LruCache
79
import androidx.core.graphics.createBitmap
810
import com.caverock.androidsvg.SVG
911
import com.caverock.androidsvg.SVGExternalFileResolver
1012
import com.facebook.react.uimanager.PixelUtil.dpToPx
13+
import com.facebook.react.uimanager.ThemedReactContext
1114
import com.google.android.gms.maps.model.BitmapDescriptor
1215
import com.google.android.gms.maps.model.BitmapDescriptorFactory
1316
import com.google.android.gms.maps.model.Marker
@@ -22,10 +25,13 @@ import kotlinx.coroutines.SupervisorJob
2225
import kotlinx.coroutines.ensureActive
2326
import kotlinx.coroutines.launch
2427
import kotlinx.coroutines.withContext
28+
import java.net.HttpURLConnection
29+
import java.net.URL
2530
import java.net.URLDecoder
2631
import kotlin.coroutines.coroutineContext
2732

2833
class MapMarkerBuilder(
34+
val context: ThemedReactContext,
2935
private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default),
3036
) {
3137
private val iconCache =
@@ -39,6 +45,7 @@ class MapMarkerBuilder(
3945
private val jobsById = mutableMapOf<String, Job>()
4046

4147
init {
48+
// / TODO: refactor with androidsvg 1.5 release
4249
SVG.registerExternalFileResolver(
4350
object : SVGExternalFileResolver() {
4451
override fun resolveImage(filename: String?): Bitmap? {
@@ -64,11 +71,70 @@ class MapMarkerBuilder(
6471
}
6572
}
6673

74+
filename.startsWith("http://") || filename.startsWith("https://") -> {
75+
val conn =
76+
(URL(filename).openConnection() as HttpURLConnection).apply {
77+
connectTimeout = 5000
78+
readTimeout = 5000
79+
requestMethod = "GET"
80+
instanceFollowRedirects = true
81+
}
82+
conn.connect()
83+
84+
val contentType = conn.contentType ?: ""
85+
val result =
86+
if (contentType.contains("svg") || filename.endsWith(".svg")) {
87+
val svgText = conn.inputStream.bufferedReader().use { it.readText() }
88+
val innerSvg = SVG.getFromString(svgText)
89+
val w = innerSvg.documentWidth.takeIf { it > 0 } ?: 128f
90+
val h = innerSvg.documentHeight.takeIf { it > 0 } ?: 128f
91+
val bmp = createBitmap(w.toInt(), h.toInt())
92+
val canvas = Canvas(bmp)
93+
innerSvg.renderToCanvas(canvas)
94+
bmp
95+
} else {
96+
conn.inputStream.use { BitmapFactory.decodeStream(it) }
97+
}
98+
99+
conn.disconnect()
100+
result
101+
}
102+
67103
else -> null
68104
}
69105
}.getOrNull()
70106
}
71107

108+
override fun resolveFont(
109+
fontFamily: String?,
110+
fontWeight: Int,
111+
fontStyle: String?,
112+
): Typeface? {
113+
if (fontFamily.isNullOrBlank()) return null
114+
115+
return runCatching {
116+
val assetManager = context.assets
117+
118+
val candidates =
119+
listOf(
120+
"fonts/$fontFamily.ttf",
121+
"fonts/$fontFamily.otf",
122+
)
123+
124+
for (path in candidates) {
125+
try {
126+
return Typeface.createFromAsset(assetManager, path)
127+
} catch (_: Throwable) {
128+
// / ignore
129+
}
130+
}
131+
132+
Typeface.create(fontFamily, Typeface.NORMAL)
133+
}.getOrElse {
134+
Typeface.create(fontFamily, fontWeight)
135+
}
136+
}
137+
72138
override fun isFormatSupported(mimeType: String?): Boolean = mimeType?.startsWith("image/") == true
73139
},
74140
)

android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class RNGoogleMapsPlusView(
2929
private var locationHandler = LocationHandler(context)
3030
private var playServiceHandler = PlayServicesHandler(context)
3131

32-
private val markerBuilder = MapMarkerBuilder()
32+
private val markerBuilder = MapMarkerBuilder(context)
3333
private val polylineBuilder = MapPolylineBuilder()
3434
private val polygonBuilder = MapPolygonBuilder()
3535
private val circleBuilder = MapCircleBuilder()

example/assets/images/red_dot.jpg

3.78 KB
Loading

example/src/screens/SvgMarkersScreen.tsx

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,46 @@ const MARKER_P_IMAGE_INLINE_SVG = `
7272
</svg>
7373
`;
7474

75+
const MARKER_P_IMAGE_REMOTE_SVG = `
76+
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
77+
<image
78+
width="64"
79+
height="64"
80+
href="https://raw.githubusercontent.com/pinpong/react-native-google-maps-plus/main/example/assets/red_dot.svg"
81+
/>
82+
${buildText('F')}
83+
</svg>
84+
`;
85+
86+
const MARKER_P_IMAGE_REMOTE_PNG = `
87+
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
88+
<image
89+
width="64"
90+
height="64"
91+
href="https://raw.githubusercontent.com/pinpong/react-native-google-maps-plus/main/example/assets/red_dot.png"
92+
/>
93+
${buildText('G')}
94+
95+
</svg>
96+
`;
97+
98+
const MARKER_P_IMAGE_REMOTE_JPG = `
99+
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
100+
<image
101+
width="64"
102+
height="64"
103+
href="https://raw.githubusercontent.com/pinpong/react-native-google-maps-plus/main/example/assets/red_dot.jpg"
104+
/>
105+
${buildText('H')}
106+
107+
</svg>
108+
`;
109+
75110
const MARKER_P_IMAGE_USE_SYMBOL = `
76111
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
77112
<g id="dot">
78113
<circle cx="32" cy="32" r="16" fill="#FF0000" stroke="#FFF" stroke-width="4" />
79-
${buildText('F')}
114+
${buildText('I')}
80115
</g>
81116
<use href="#dot" />
82117
@@ -92,7 +127,7 @@ const MARKER_P_IMAGE_GRADIENT = `
92127
</linearGradient>
93128
</defs>
94129
<circle cx="32" cy="32" r="16" fill="url(#grad)" stroke="#FFF" stroke-width="4" />
95-
${buildText('G')}
130+
${buildText('J')}
96131
97132
</svg>
98133
`;
@@ -109,7 +144,7 @@ const MARKER_P_IMAGE_TRANSPARENT = `
109144
stroke="#FFFFFF"
110145
stroke-width="4"
111146
/>
112-
${buildText('H')}
147+
${buildText('K')}
113148
</svg>
114149
`;
115150

@@ -153,6 +188,9 @@ const MARKERS = [
153188
MARKER_P_IMAGE_SVG_BASE64,
154189
MARKER_P_IMAGE_BASE64_PNG,
155190
MARKER_P_IMAGE_INLINE_SVG,
191+
MARKER_P_IMAGE_REMOTE_SVG,
192+
MARKER_P_IMAGE_REMOTE_PNG,
193+
MARKER_P_IMAGE_REMOTE_JPG,
156194
MARKER_P_IMAGE_USE_SYMBOL,
157195
MARKER_P_IMAGE_GRADIENT,
158196
MARKER_P_IMAGE_TRANSPARENT,

0 commit comments

Comments
 (0)