1import { Asset } from 'expo-asset'; 2import React from 'react'; 3 4import { Circle, CircleObject } from './Circle'; 5import { Cluster, ClusterObject } from './Cluster'; 6import { Color } from './Common.types'; 7import { GeoJson, GeoJsonObject } from './GeoJson'; 8import { Heatmap, HeatmapObject } from './Heatmap'; 9import { KML, KMLObject } from './KML'; 10import { Marker, MarkerObject } from './Marker'; 11import { Overlay, OverlayObject } from './Overlay'; 12import { Polygon, PolygonObject } from './Polygon'; 13import { Polyline, PolylineObject } from './Polyline'; 14 15export function isSimpleType(child: any) { 16 return ( 17 typeof child == 'string' || 18 typeof child == 'boolean' || 19 typeof child == 'number' || 20 child === null || 21 child === undefined 22 ); 23} 24 25export function isPolygon(child: any): child is Polygon { 26 if ('type' in child && String(child.type).includes('Polygon') && 'props' in child) { 27 const props = Object.keys(child.props); 28 if (props.includes('points')) { 29 return true; 30 } 31 } 32 return false; 33} 34 35export function isPolyline(child: any): child is Polyline { 36 if ('type' in child && String(child.type).includes('Polyline') && 'props' in child) { 37 const props = Object.keys(child.props); 38 if (props.includes('points')) { 39 return true; 40 } 41 } 42 return false; 43} 44 45export function isOverlay(child: any): child is Overlay { 46 if ('type' in child && String(child.type).includes('Overlay') && 'props' in child) { 47 const props = Object.keys(child.props); 48 if (props.includes('bounds') && props.includes('icon')) { 49 return true; 50 } 51 } 52 return false; 53} 54 55export function isCircle(child: any): child is Circle { 56 if ('type' in child && String(child.type).includes('Circle') && 'props' in child) { 57 const props = Object.keys(child.props); 58 if (props.includes('center') && props.includes('radius')) { 59 return true; 60 } 61 } 62 return false; 63} 64 65export function isMarker(child: any): child is Marker { 66 if ('type' in child && String(child.type).includes('Marker') && 'props' in child) { 67 const props = Object.keys(child.props); 68 if (props.includes('latitude') && props.includes('longitude')) { 69 return true; 70 } 71 } 72 return false; 73} 74 75export function isCluster(child: any): child is Cluster { 76 if ('type' in child && String(child.type).includes('Cluster') && 'props' in child) { 77 const props = Object.keys(child.props); 78 if (props.includes('name')) { 79 return true; 80 } 81 } 82 return false; 83} 84 85export function isKML(child: any): child is KML { 86 if ('type' in child && String(child.type).includes('KML') && 'props' in child) { 87 const props = Object.keys(child.props); 88 if (props.includes('filePath')) { 89 return true; 90 } 91 } 92 return false; 93} 94 95export function isGeoJson(child: any): child is GeoJson { 96 if ('type' in child && String(child.type).includes('GeoJson') && 'props' in child) { 97 const props = Object.keys(child.props); 98 if (props.includes('geoJsonString')) { 99 return true; 100 } 101 } 102 return false; 103} 104 105export function isHeatmap(child: any): child is Heatmap { 106 if ('type' in child && String(child.type).includes('Heatmap') && 'props' in child) { 107 const props = Object.keys(child.props); 108 if (props.includes('points')) { 109 return true; 110 } 111 } 112 return false; 113} 114 115export function isHexColor(color: any): color is Color { 116 return color.length > 0 && color[0] === '#'; 117} 118 119export function mapColorToHexColor(color: Color, defaultColor?: string): string { 120 let defColor = defaultColor; 121 if (defColor === undefined) defColor = '#000000'; 122 123 const colors: Record<Color | 'default', string> = { 124 red: '#ff0000', 125 blue: '#0000ff', 126 green: '#00ff00', 127 black: '#000000', 128 white: '#ffffff', 129 gray: '#808080', 130 cyan: '#00ffff', 131 magenta: '#ff00ff', 132 yellow: '#ffff00', 133 lightgray: '#d3d3d3', 134 darkgray: '#a9a9a9', 135 aqua: '#00ffff', 136 fuchsia: '#ca2c92', 137 lime: '#bfff00', 138 maroon: '#800000', 139 navy: '#000080', 140 olive: '#808000', 141 purple: '#800080', 142 silver: '#c0c0c0', 143 teal: '#008080', 144 default: defColor, 145 }; 146 147 return colors[color] || colors['default']; 148} 149 150export function warnIfChildIsIncompatible(child: any) { 151 if (typeof child == 'string' || typeof child == 'boolean' || typeof child == 'number') { 152 console.warn(`Warning! Child of type ${typeof child} isn't valid ExpoMap child!`); 153 } else if (child != null && child !== undefined) { 154 console.log(child.type); 155 console.warn( 156 `Warning! Child of type ${(child as React.ReactElement<any>).type} isn't valid ExpoMap child!` 157 ); 158 } 159} 160 161export function buildGeoJsonObject(child: GeoJson): GeoJsonObject { 162 if ( 163 child.props.defaultStyle?.marker?.color !== undefined && 164 !isHexColor(child.props.defaultStyle.marker.color) 165 ) { 166 child.props.defaultStyle.marker.color = mapColorToHexColor( 167 child.props.defaultStyle?.marker.color as Color, 168 '#ff0000' 169 ); 170 } 171 172 if ( 173 child.props.defaultStyle?.polygon?.fillColor !== undefined && 174 !isHexColor(child.props.defaultStyle.polygon.fillColor) 175 ) { 176 child.props.defaultStyle.polygon.fillColor = mapColorToHexColor( 177 child.props.defaultStyle.polygon.fillColor as Color 178 ); 179 } 180 181 if ( 182 child.props.defaultStyle?.polygon?.strokeColor !== undefined && 183 !isHexColor(child.props.defaultStyle.polygon.strokeColor) 184 ) { 185 child.props.defaultStyle.polygon.strokeColor = mapColorToHexColor( 186 child.props.defaultStyle.polygon.strokeColor as Color 187 ); 188 } 189 190 if ( 191 child.props.defaultStyle?.polyline?.color !== undefined && 192 !isHexColor(child.props.defaultStyle.polyline.color) 193 ) { 194 child.props.defaultStyle.polyline.color = mapColorToHexColor( 195 child.props.defaultStyle.polyline.color as Color 196 ); 197 } 198 199 return { 200 type: 'geojson', 201 geoJsonString: child.props.geoJsonString, 202 defaultStyle: child.props.defaultStyle, 203 } as GeoJsonObject; 204} 205 206export async function buildMarkerObject(child: Marker): Promise<MarkerObject> { 207 let iconPath: Asset | undefined = undefined; 208 if (child.props.icon !== undefined) { 209 iconPath = await Asset.fromModule(child.props.icon).downloadAsync(); 210 } 211 212 const markerObject = { 213 type: 'marker', 214 id: child.props.id, 215 latitude: child.props.latitude, 216 longitude: child.props.longitude, 217 markerTitle: child.props.markerTitle, 218 markerSnippet: child.props.markerSnippet, 219 icon: iconPath?.localUri, 220 color: child.props.color, 221 draggable: child.props.draggable ? child.props.draggable : false, 222 anchorU: child.props.anchorU, 223 anchorV: child.props.anchorV, 224 opacity: child.props.opacity ? child.props.opacity : 1, 225 } as MarkerObject; 226 227 if (markerObject.color !== undefined && !isHexColor(markerObject.color)) { 228 markerObject.color = mapColorToHexColor(markerObject.color as Color, '#ff0000'); 229 } 230 return markerObject; 231} 232 233export async function buildOverlayObject(child: Overlay): Promise<OverlayObject> { 234 const iconPath = await Asset.fromModule(child.props.icon).downloadAsync(); 235 236 const overlayObject = { 237 type: 'overlay', 238 bounds: child.props.bounds, 239 icon: iconPath.localUri, 240 } as OverlayObject; 241 242 return overlayObject; 243} 244 245export function buildPolygonObject(child: Polygon): PolygonObject { 246 const polygonObject = { 247 type: 'polygon', 248 points: child.props.points, 249 fillColor: child.props.fillColor, 250 strokeColor: child.props.strokeColor, 251 strokeWidth: child.props.strokeWidth, 252 strokePattern: child.props.strokePattern, 253 jointType: child.props.jointType, 254 } as PolygonObject; 255 if (polygonObject.fillColor !== undefined && !isHexColor(polygonObject.fillColor)) { 256 polygonObject.fillColor = mapColorToHexColor(polygonObject.fillColor as Color); 257 } 258 return polygonObject; 259} 260 261export function buildPolylineObject(child: Polyline): PolylineObject { 262 const polylineObject = { 263 type: 'polyline', 264 points: child.props.points, 265 color: child.props.color, 266 width: child.props.width, 267 pattern: child.props.pattern, 268 jointType: child.props.jointType, 269 capType: child.props.capType, 270 } as PolylineObject; 271 if (polylineObject.color !== undefined && !isHexColor(polylineObject.color)) { 272 polylineObject.color = mapColorToHexColor(polylineObject.color as Color); 273 } 274 return polylineObject; 275} 276 277export function buildCircleObject(child: Circle): CircleObject { 278 return { 279 type: 'circle', 280 center: child.props.center, 281 radius: child.props.radius, 282 fillColor: child.props.fillColor, 283 strokeColor: child.props.strokeColor, 284 strokeWidth: child.props.strokeWidth, 285 } as CircleObject; 286} 287 288export async function buildKMLObject(child: KML): Promise<KMLObject> { 289 const filePath = await Asset.fromModule(child.props.filePath).downloadAsync(); 290 return { 291 type: 'kml', 292 filePath: filePath.localUri, 293 } as KMLObject; 294} 295 296export async function buildClusterObject(child: Cluster): Promise<ClusterObject | null> { 297 const clusterChildrenArray = React.Children.map(child.props.children, async (clusterChild) => { 298 if (!isSimpleType(clusterChild)) { 299 if (isMarker(clusterChild)) { 300 return buildMarkerObject(clusterChild); 301 } 302 } 303 warnIfChildIsIncompatible(clusterChild); 304 return null; 305 }); 306 307 if (clusterChildrenArray !== undefined) { 308 let iconPath: Asset | undefined = undefined; 309 if (child.props.icon !== undefined) { 310 iconPath = await Asset.fromModule(child.props.icon).downloadAsync(); 311 } 312 313 // TODO(@lukmccall): remove any cast 314 const clusterPropObjects = await Promise.all(clusterChildrenArray as any); 315 let minimumClusterSize: number; 316 317 if (child.props.minimumClusterSize !== undefined && child.props.minimumClusterSize > 0) { 318 minimumClusterSize = child.props.minimumClusterSize; 319 } else { 320 minimumClusterSize = 4; 321 } 322 323 const clusterObject = { 324 type: 'cluster', 325 id: child.props.id, 326 markers: clusterPropObjects, 327 name: child.props.name, 328 minimumClusterSize, 329 markerTitle: child.props.markerTitle, 330 markerSnippet: child.props.markerSnippet, 331 icon: iconPath?.localUri, 332 color: child.props.color, 333 opacity: child.props.opacity ? child.props.opacity : 1, 334 } as ClusterObject; 335 336 if (clusterObject.color !== undefined && !isHexColor(clusterObject.color)) { 337 clusterObject.color = mapColorToHexColor(clusterObject.color as Color, '#ff0000'); 338 } 339 return clusterObject; 340 } 341 return null; 342} 343 344export function buildHeatmapObject(child: Heatmap): HeatmapObject { 345 return { 346 type: 'heatmap', 347 points: child.props.points, 348 radius: child.props.radius, 349 gradient: child.props.gradient, 350 opacity: child.props.opacity, 351 } as HeatmapObject; 352} 353