Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-21 15:11:39 +01:00
parent d6b5d53060
commit d90a1dc8df
2145 changed files with 210227 additions and 2 deletions

1
maps-utils/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

10
maps-utils/README.md Normal file
View file

@ -0,0 +1,10 @@
# Maps utils
Fork of [Maps SDK for Android Utility Library](https://github.com/googlemaps/android-maps-utils).
Includes code up to the [2025-08-06 commit](https://github.com/googlemaps/android-maps-utils/commit/014c64e7d06c98864092b58f701b20e301ef8d30).
Differences with original library:
- Only supports a small subset of features we need for Breezy Weather
- Add an utility to decode [polylines](https://developers.google.com/maps/documentation/utilities/polylinealgorithm?hl=en) (no longer used by Breezy Weather, but still there just in case)
- No dependency on proprietary Google Play Services
- Rewritten in Kotlin

View file

@ -0,0 +1,18 @@
plugins {
id("breezy.library")
kotlin("android")
}
android {
namespace = "com.google.maps.android"
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
}
dependencies {
testImplementation(libs.bundles.test)
testRuntimeOnly(libs.junit.platform)
}

View file

21
maps-utils/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,68 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android
import com.google.maps.android.model.LatLng
/**
* Ported from https://github.com/googlemaps/js-polyline-codec/blob/main/src/index.ts
* by Breezy Weather
*/
object EncodedPolylineUtil {
fun decode(encodedPath: String): List<LatLng> {
val factor = 1E5
val coordinatesList = mutableListOf<LatLng>()
var index = 0
var lat = 0
var lng = 0
// This code has been profiled and optimized, so don't modify it without
// measuring its performance.
while (index < encodedPath.length) {
// Fully unrolling the following loops speeds things up about 5%.
var result = 1
var shift = 0
var b: Int
do {
// Invariant: "result" is current partial result plus (1 << shift).
// The following line effectively clears this bit by decrementing "b".
b = encodedPath[index++].code - 63 - 1
result += b shl shift
shift += 5
} while (b >= 0x1f) // See note above.
lat += if (result and 1 != 0) (result shr 1).inv() else result shr 1
result = 1
shift = 0
do {
b = encodedPath[index++].code - 63 - 1
result += b shl shift
shift += 5
} while (b >= 0x1f)
lng += if (result and 1 != 0) (result shr 1).inv() else result shr 1
coordinatesList.add(
LatLng(
lat.toDouble() / factor,
lng.toDouble() / factor
)
)
}
return coordinatesList
}
}

View file

@ -0,0 +1,89 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android
import kotlin.math.asin
import kotlin.math.cos
import kotlin.math.ln
import kotlin.math.sin
import kotlin.math.sqrt
import kotlin.math.tan
/**
* Utility functions that are used my both PolyUtil and SphericalUtil.
*/
internal object MathUtil {
/**
* The earth's radius, in meters.
* Mean radius as defined by IUGG.
*/
const val EARTH_RADIUS = 6371009.0
/**
* Wraps the given value into the inclusive-exclusive interval between min and max.
*
* @param n The value to wrap.
* @param min The minimum.
* @param max The maximum.
*/
fun wrap(n: Double, min: Double, max: Double): Double {
return if (n >= min && n < max) n else mod(n - min, max - min) + min
}
/**
* Returns the non-negative remainder of x / m.
*
* @param x The operand.
* @param m The modulus.
*/
fun mod(x: Double, m: Double): Double {
return (x % m + m) % m
}
/**
* Returns mercator Y corresponding to latitude.
* See https://en.wikipedia.org/wiki/Mercator_projection .
*/
fun mercator(lat: Double): Double {
return ln(tan(lat * 0.5 + Math.PI / 4))
}
/**
* Returns haversine(angle-in-radians).
* hav(x) == (1 - cos(x)) / 2 == sin(x / 2)^2.
*/
fun hav(x: Double): Double {
val sinHalf: Double = sin(x * 0.5)
return sinHalf * sinHalf
}
/**
* Computes inverse haversine. Has good numerical stability around 0.
* arcHav(x) == acos(1 - 2 * x) == 2 * asin(sqrt(x)).
* The argument must be in [0, 1], and the result is positive.
*/
fun arcHav(x: Double): Double {
return 2 * asin(sqrt(x))
}
/**
* Returns hav() of distance from (lat1, lng1) to (lat2, lng2) on the unit sphere.
*/
fun havDistance(lat1: Double, lat2: Double, dLng: Double): Double {
return hav(lat1 - lat2) + hav(dLng) * cos(lat1) * cos(lat2)
}
}

View file

@ -0,0 +1,143 @@
/*
* Copyright 2008, 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android
import com.google.maps.android.MathUtil.wrap
import com.google.maps.android.model.LatLng
import kotlin.math.sin
import kotlin.math.tan
object PolyUtil {
/**
* Returns tan(latitude-at-lng3) on the great circle (lat1, lng1) to (lat2, lng2). lng1==0.
* See http://williams.best.vwh.net/avform.htm .
*/
private fun tanLatGC(lat1: Double, lat2: Double, lng2: Double, lng3: Double): Double {
return (tan(lat1) * sin(lng2 - lng3) + tan(lat2) * sin(lng3)) / sin(lng2)
}
/**
* Returns mercator(latitude-at-lng3) on the Rhumb line (lat1, lng1) to (lat2, lng2). lng1==0.
*/
private fun mercatorLatRhumb(lat1: Double, lat2: Double, lng2: Double, lng3: Double): Double {
return (MathUtil.mercator(lat1) * (lng2 - lng3) + MathUtil.mercator(lat2) * lng3) / lng2
}
/**
* Computes whether the vertical segment (lat3, lng3) to South Pole intersects the segment
* (lat1, lng1) to (lat2, lng2).
* Longitudes are offset by -lng1; the implicit lng1 becomes 0.
*/
private fun intersects(
lat1: Double,
lat2: Double,
lng2: Double,
lat3: Double,
lng3: Double,
geodesic: Boolean,
): Boolean {
// Both ends on the same side of lng3.
if (lng3 >= 0 && lng3 >= lng2 || lng3 < 0 && lng3 < lng2) {
return false
}
// Point is South Pole.
if (lat3 <= -Math.PI / 2) {
return false
}
// Any segment end is a pole.
if (lat1 <= -Math.PI / 2 || lat2 <= -Math.PI / 2 || lat1 >= Math.PI / 2 || lat2 >= Math.PI / 2) {
return false
}
if (lng2 <= -Math.PI) {
return false
}
val linearLat = (lat1 * (lng2 - lng3) + lat2 * lng3) / lng2
// Northern hemisphere and point under lat-lng line.
if (lat1 >= 0 && lat2 >= 0 && lat3 < linearLat) {
return false
}
// Southern hemisphere and point above lat-lng line.
if (lat1 <= 0 && lat2 <= 0 && lat3 >= linearLat) {
return true
}
// North Pole.
if (lat3 >= Math.PI / 2) {
return true
}
// Compare lat3 with latitude on the GC/Rhumb segment corresponding to lng3.
// Compare through a strictly-increasing function (tan() or mercator()) as convenient.
return if (geodesic) {
tan(lat3) >= tanLatGC(lat1, lat2, lng2, lng3)
} else {
MathUtil.mercator(lat3) >= mercatorLatRhumb(lat1, lat2, lng2, lng3)
}
}
fun containsLocation(point: LatLng, polygon: List<LatLng>, geodesic: Boolean): Boolean {
return containsLocation(point.latitude, point.longitude, polygon, geodesic)
}
/**
* Computes whether the given point lies inside the specified polygon.
* The polygon is always considered closed, regardless of whether the last point equals
* the first or not.
* Inside is defined as not containing the South Pole -- the South Pole is always outside.
* The polygon is formed of great circle segments if geodesic is true, and of rhumb
* (loxodromic) segments otherwise.
*/
fun containsLocation(
latitude: Double,
longitude: Double,
polygon: List<LatLng>,
geodesic: Boolean,
): Boolean {
val size = polygon.size
if (size == 0) {
return false
}
val lat3 = Math.toRadians(latitude)
val lng3 = Math.toRadians(longitude)
val prev = polygon[size - 1]
var lat1 = Math.toRadians(prev.latitude)
var lng1 = Math.toRadians(prev.longitude)
var nIntersect = 0
for (point2 in polygon) {
val dLng3 = wrap(lng3 - lng1, -Math.PI, Math.PI)
// Special case: point equal to vertex is inside.
if (lat3 == lat1 && dLng3 == 0.0) {
return true
}
val lat2 = Math.toRadians(point2.latitude)
val lng2 = Math.toRadians(point2.longitude)
// Offset longitudes by -lng1.
if (
intersects(
lat1,
lat2,
wrap(lng2 - lng1, -Math.PI, Math.PI),
lat3,
dLng3,
geodesic
)
) {
++nIntersect
}
lat1 = lat2
lng1 = lng2
}
return nIntersect and 1 != 0
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android
import com.google.maps.android.MathUtil.EARTH_RADIUS
import com.google.maps.android.MathUtil.arcHav
import com.google.maps.android.MathUtil.havDistance
import com.google.maps.android.model.LatLng
object SphericalUtil {
/**
* Returns distance on the unit sphere; the arguments are in radians.
*/
private fun distanceRadians(lat1: Double, lng1: Double, lat2: Double, lng2: Double): Double {
return arcHav(havDistance(lat1, lat2, lng1 - lng2))
}
/**
* Returns the angle between two LatLngs, in radians. This is the same as the distance
* on the unit sphere.
*/
fun computeAngleBetween(from: LatLng, to: LatLng): Double {
return distanceRadians(
Math.toRadians(from.latitude),
Math.toRadians(from.longitude),
Math.toRadians(to.latitude),
Math.toRadians(to.longitude)
)
}
/**
* Returns the distance between two LatLngs, in meters.
*/
fun computeDistanceBetween(from: LatLng, to: LatLng): Double {
return computeAngleBetween(from, to) * EARTH_RADIUS
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data
import com.google.maps.android.data.geojson.GeoJsonPolygon
import com.google.maps.android.model.LatLng
/**
* An interface containing the common properties of
* [GeoJsonPolygon] and
* [KmlPolygon][com.google.maps.android.data.kml.KmlPolygon]
*
* @param <T> the type of Polygon - GeoJsonPolygon or KmlPolygon
</T> */
interface DataPolygon<T> : Geometry<Any?> {
/**
* Gets an array of outer boundary coordinates
*
* @return array of outer boundary coordinates
*/
val outerBoundaryCoordinates: List<LatLng?>?
/**
* Gets an array of arrays of inner boundary coordinates
*
* @return array of arrays of inner boundary coordinates
*/
val innerBoundaryCoordinates: List<List<LatLng?>?>
}

View file

@ -0,0 +1,131 @@
/*
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data
import com.google.maps.android.data.geojson.GeoJsonFeature
/**
* An abstraction that shares the common properties of
* [KmlPlacemark][com.google.maps.android.data.kml.KmlPlacemark] and
* [GeoJsonFeature]
* @param featureGeometry type of geometry to assign to the feature
* @param id common identifier of the feature
* @param properties map containing properties related to the feature
*/
open class Feature(
/**
* Sets the stored Geometry and redraws it on the layer if it has already been added
*
* @param geometry Geometry to set
*/
/**
* Sets the stored Geometry and redraws it on the layer if it has already been added
*
* @param geometry Geometry to set
*/
var geometry: Geometry<*>?,
/**
* Gets the id of the feature
*
* @return id
*/
id: String?,
properties: MutableMap<String, String?>?,
) {
var mId: String? = id
protected set
private var mProperties: MutableMap<String, String?> = properties ?: mutableMapOf()
/**
* Gets the geometry object
*
* @return geometry object
*/
/**
* Returns all the stored property keys
*
* @return iterable of property keys
*/
val propertyKeys: Iterable<String>
get() = mProperties.keys
/**
* Gets the property entry set
*
* @return property entry set
*/
val properties: Iterable<*>
get() = mProperties.entries
/**
* Gets the value for a stored property
*
* @param property key of the property
* @return value of the property if its key exists, otherwise null
*/
fun getProperty(property: String): String? {
return mProperties[property]
}
/**
* Checks whether the given property key exists
*
* @param property key of the property to check
* @return true if property key exists, false otherwise
*/
fun hasProperty(property: String): Boolean {
return mProperties.containsKey(property)
}
/**
* Gets whether the placemark has properties
*
* @return true if there are properties in the properties map, false otherwise
*/
fun hasProperties(): Boolean {
return mProperties.isNotEmpty()
}
/**
* Checks if the geometry is assigned
*
* @return true if feature contains geometry object, otherwise null
*/
fun hasGeometry(): Boolean {
return geometry != null
}
/**
* Store a new property key and value
*
* @param property key of the property to store
* @param propertyValue value of the property to store
* @return previous value with the same key, otherwise null if the key didn't exist
*/
protected open fun setProperty(property: String, propertyValue: String?): String? {
return mProperties.put(property, propertyValue)
}
/**
* Removes a given property
*
* @param property key of the property to remove
* @return value of the removed property or null if there was no corresponding key
*/
protected open fun removeProperty(property: String): String? {
return mProperties.remove(property)
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data
/**
* An abstraction that represents a Geometry object
*
* @param <T> the type of Geometry object
</T> */
interface Geometry<T> {
/**
* Gets the type of geometry
*
* @return type of geometry
*/
val geometryType: String
/**
* Gets the stored KML Geometry object
*
* @return geometry object
*/
val geometryObject: T
}

View file

@ -0,0 +1,47 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data
import com.google.maps.android.data.geojson.GeoJsonLineString
import com.google.maps.android.model.LatLng
/**
* An abstraction that shares the common properties of
* [KmlLineString] and
* [GeoJsonLineString]
* @param coordinates array of coordinates
*/
open class LineString(
coordinates: List<LatLng>,
) : Geometry<List<LatLng>> {
override val geometryType: String = GEOMETRY_TYPE
/**
* Gets the coordinates of the LineString
*
* @return coordinates of the LineString
*/
override val geometryObject: List<LatLng> = coordinates
override fun toString(): String {
return """${GEOMETRY_TYPE}{ coordinates=$geometryObject}"""
}
companion object {
const val GEOMETRY_TYPE = "LineString"
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data
import com.google.maps.android.data.geojson.GeoJsonMultiLineString
import com.google.maps.android.data.geojson.GeoJsonMultiPoint
import com.google.maps.android.data.geojson.GeoJsonMultiPolygon
/**
* An abstraction that shares the common properties of
* [KmlMultiGeometry] and [GeoJsonMultiLineString], [GeoJsonMultiPoint] and [GeoJsonMultiPolygon]
* @param geometries contains list of Polygons, Linestrings or Points
*/
open class MultiGeometry(
geometries: List<Geometry<*>>,
) : Geometry<Any> {
/**
* Gets the type of geometry
*
* @return type of geometry
*/
/**
* Set the type of geometry
*
* @param type String describing type of geometry
*/
override var geometryType = "MultiGeometry"
private val mGeometries: List<Geometry<*>> = geometries
/**
* Gets the stored geometry object
*
* @return geometry object
*/
override val geometryObject: Any
get() = mGeometries
override fun toString(): String {
var typeString = "Geometries="
if (geometryType == "MultiPoint") {
typeString = "LineStrings="
}
if (geometryType == "MultiLineString") {
typeString = "points="
}
if (geometryType == "MultiPolygon") {
typeString = "Polygons="
}
return """$geometryType{ $typeString$geometryObject}"""
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data
import com.google.maps.android.data.geojson.GeoJsonPoint
import com.google.maps.android.model.LatLng
/**
* An abstraction that shares the common properties of
* [KmlPoint] and
* [GeoJsonPoint]
*/
open class Point(
coordinates: LatLng,
) : Geometry<Any> {
private val mCoordinates: LatLng
/**
* Creates a new Point object
*
* @param coordinates coordinates of Point to store
*/
init {
mCoordinates = coordinates
}
override val geometryObject: Any
/**
* Gets the coordinates of the Point
*
* @return coordinates of the Point
*/
get() = mCoordinates
override fun toString(): String {
return """$geometryType{ coordinates=$mCoordinates}"""
}
override val geometryType = "Point"
}

View file

@ -0,0 +1,71 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data.geojson
import com.google.maps.android.data.Feature
import com.google.maps.android.data.Geometry
import com.google.maps.android.model.LatLngBounds
/**
* A GeoJsonFeature has a geometry, bounding box, id and set of properties. Styles are also stored
* in this class.
*/
class GeoJsonFeature(
geometry: Geometry<*>?,
id: String?,
properties: HashMap<String, String?>?,
boundingBox: LatLngBounds?,
) : Feature(geometry, id, properties) {
private val mBoundingBox: LatLngBounds?
/**
* Creates a new GeoJsonFeature object
*
* @param geometry type of geometry to assign to the feature
* @param id common identifier of the feature
* @param properties hashmap of containing properties related to the feature
* @param boundingBox bounding box of the feature
*/
init {
mId = id
mBoundingBox = boundingBox
}
/**
* Store a new property key and value
*
* @param property key of the property to store
* @param propertyValue value of the property to store
* @return previous value with the same key, otherwise null if the key didn't exist
*/
public override fun setProperty(property: String, propertyValue: String?): String? {
return super.setProperty(property, propertyValue)
}
/**
* Removes a given property
*
* @param property key of the property to remove
* @return value of the removed property or null if there was no corresponding key
*/
public override fun removeProperty(property: String): String? {
return super.removeProperty(property)
}
override fun toString(): String {
return """Feature{ bounding box=$mBoundingBox, geometry=$geometry, id=$mId, properties=$properties}"""
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright 2020 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data.geojson
import com.google.maps.android.data.Geometry
import com.google.maps.android.data.MultiGeometry
/**
* A GeoJsonGeometryCollection geometry contains a number of GeoJson Geometry objects.
*/
class GeoJsonGeometryCollection(
geometries: List<Geometry<*>>,
) : MultiGeometry(geometries) {
/**
* Creates a new GeoJsonGeometryCollection object
*
* @param geometries array of Geometry objects to add to the GeoJsonGeometryCollection
*/
init {
geometryType = "GeometryCollection"
}
/**
* Gets the type of geometry. The type of geometry conforms to the GeoJSON 'type'
* specification.
*
* @return type of geometry
*/
val type: String
get() = geometryType
}

View file

@ -0,0 +1,44 @@
/*
* Copyright 2020 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data.geojson
import com.google.maps.android.data.LineString
import com.google.maps.android.model.LatLng
/**
* A GeoJsonLineString geometry represents a number of connected [ ]s.
* @param coordinates array of coordinates
*/
class GeoJsonLineString(
coordinates: List<LatLng>,
) : LineString(coordinates) {
/**
* Gets the type of geometry. The type of geometry conforms to the GeoJSON 'type'
* specification.
*
* @return type of geometry
*/
val type: String
get() = geometryType
/**
* Gets the coordinates of the GeoJsonLineString
*
* @return list of coordinates of the GeoJsonLineString
*/
val coordinates: List<LatLng>
get() = geometryObject
}

View file

@ -0,0 +1,43 @@
/*
* Copyright 2020 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data.geojson
import com.google.maps.android.data.MultiGeometry
/**
* A GeoJsonMultiLineString geometry contains a number of [GeoJsonLineString]s.
*/
class GeoJsonMultiLineString(
geoJsonLineStrings: List<GeoJsonLineString>,
) : MultiGeometry(geoJsonLineStrings) {
/**
* Creates a new GeoJsonMultiLineString object
*
* @param geoJsonLineStrings list of GeoJsonLineStrings to store
*/
init {
geometryType = "MultiLineString"
}
val type: String
/**
* Gets the type of geometry. The type of geometry conforms to the GeoJSON 'type'
* specification.
*
* @return type of geometry
*/
get() = geometryType
}

View file

@ -0,0 +1,43 @@
/*
* Copyright 2020 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data.geojson
import com.google.maps.android.data.MultiGeometry
/**
* A GeoJsonMultiPoint geometry contains a number of [GeoJsonPoint]s.
*/
class GeoJsonMultiPoint(
geoJsonPoints: List<GeoJsonPoint>,
) : MultiGeometry(geoJsonPoints) {
/**
* Creates a GeoJsonMultiPoint object
*
* @param geoJsonPoints list of GeoJsonPoints to store
*/
init {
geometryType = "MultiPoint"
}
/**
* Gets the type of geometry. The type of geometry conforms to the GeoJSON 'type'
* specification.
*
* @return type of geometry
*/
val type: String
get() = geometryType
}

View file

@ -0,0 +1,59 @@
/*
* Copyright 2020 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data.geojson
import com.google.maps.android.data.MultiGeometry
/**
* A GeoJsonMultiPolygon geometry contains a number of [GeoJsonPolygon]s.
*
* @param geoJsonPolygons list of GeoJsonPolygons to store
*/
class GeoJsonMultiPolygon(
geoJsonPolygons: List<GeoJsonPolygon>,
) : MultiGeometry(geoJsonPolygons) {
/**
* Creates a new GeoJsonMultiPolygon
*/
init {
geometryType = "MultiPolygon"
}
/**
* Gets the type of geometry. The type of geometry conforms to the GeoJSON 'type'
* specification.
*
* @return type of geometry
*/
val type: String
get() = geometryType
/**
* Gets a list of GeoJsonPolygons
*
* @return list of GeoJsonPolygons
*/
val polygons: List<GeoJsonPolygon>
get() {
// convert list of Geometry types to list of GeoJsonPolygon types
val geometryList = geometryObject
val geoJsonPolygon = mutableListOf<GeoJsonPolygon>()
for (geometry in geometryList as List<GeoJsonPolygon>) {
geoJsonPolygon.add(geometry)
}
return geoJsonPolygon
}
}

View file

@ -0,0 +1,491 @@
/*
* Copyright 2020 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data.geojson
import android.util.Log
import com.google.maps.android.data.Geometry
import com.google.maps.android.model.LatLng
import com.google.maps.android.model.LatLngBounds
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
/**
* Parses a JSONObject and places data into their appropriate GeoJsonFeature objects. Returns an
* array of
* GeoJsonFeature objects parsed from the GeoJSON file.
*/
class GeoJsonParser(
private val mGeoJsonFile: JSONObject,
) {
/**
* Gets the array of GeoJsonFeature objects
*
* @return array of GeoJsonFeatures
*/
val features = mutableListOf<GeoJsonFeature>()
private var mBoundingBox: LatLngBounds? = null
/**
* Internal helper class to store latLng and altitude in a single object.
* This allows to parse [lon,lat,altitude] tuples in GeoJson files more efficiently.
* Note that altitudes are generally optional so they can be null.
*/
private class LatLngAlt(val latLng: LatLng, val altitude: Double?)
/**
* Creates a new GeoJsonParser
*
* @param geoJsonFile GeoJSON file to parse
*/
init {
parseGeoJson()
}
/**
* Parses the GeoJSON file by type and adds the generated GeoJsonFeature objects to the
* mFeatures array. Supported GeoJSON types include feature, feature collection and geometry.
*/
private fun parseGeoJson() {
try {
val feature: GeoJsonFeature?
val type = mGeoJsonFile.getString("type")
if (type == FEATURE) {
feature = parseFeature(mGeoJsonFile)
if (feature != null) {
features.add(feature)
}
} else if (type == FEATURE_COLLECTION) {
features.addAll(parseFeatureCollection(mGeoJsonFile))
} else if (isGeometry(type)) {
feature = parseGeometryToFeature(mGeoJsonFile)
if (feature != null) {
// Don't add null features
features.add(feature)
}
} else {
Log.w(LOG_TAG, "GeoJSON file could not be parsed.")
}
} catch (e: JSONException) {
Log.w(LOG_TAG, "GeoJSON file could not be parsed.")
}
}
/**
* Parses the array of GeoJSON features in a given GeoJSON feature collection. Also parses the
* bounding box member of the feature collection if it exists.
*
* @param geoJsonFeatureCollection feature collection to parse
* @return array of GeoJsonFeature objects
*/
private fun parseFeatureCollection(geoJsonFeatureCollection: JSONObject): List<GeoJsonFeature> {
val geoJsonFeatures: JSONArray
val features = mutableListOf<GeoJsonFeature>()
try {
geoJsonFeatures = geoJsonFeatureCollection.getJSONArray(FEATURE_COLLECTION_ARRAY)
if (geoJsonFeatureCollection.has(BOUNDING_BOX)) {
mBoundingBox = parseBoundingBox(
geoJsonFeatureCollection.getJSONArray(BOUNDING_BOX)
)
}
} catch (e: JSONException) {
Log.w(LOG_TAG, "Feature Collection could not be created.")
return features
}
for (i in 0 until geoJsonFeatures.length()) {
try {
val feature = geoJsonFeatures.getJSONObject(i)
if (feature.getString("type") == FEATURE) {
val parsedFeature = parseFeature(feature)
if (parsedFeature != null) {
// Don't add null features
features.add(parsedFeature)
} else {
Log.w(
LOG_TAG,
"Index of Feature in Feature Collection that could not be created: $i"
)
}
}
} catch (e: JSONException) {
Log.w(
LOG_TAG,
"Index of Feature in Feature Collection that could not be created: $i"
)
}
}
return features
}
companion object {
private const val LOG_TAG = "GeoJsonParser"
// Feature object type
private const val FEATURE = "Feature"
// Feature object geometry member
private const val FEATURE_GEOMETRY = "geometry"
// Feature object id member
private const val FEATURE_ID = "id"
// FeatureCollection type
private const val FEATURE_COLLECTION = "FeatureCollection"
// FeatureCollection features array member
private const val FEATURE_COLLECTION_ARRAY = "features"
// Geometry coordinates member
private const val GEOMETRY_COORDINATES_ARRAY = "coordinates"
// GeometryCollection type
private const val GEOMETRY_COLLECTION = "GeometryCollection"
// GeometryCollection geometries array member
private const val GEOMETRY_COLLECTION_ARRAY = "geometries"
// Coordinates for bbox
private const val BOUNDING_BOX = "bbox"
private const val PROPERTIES = "properties"
private const val POINT = "Point"
private const val MULTIPOINT = "MultiPoint"
private const val LINESTRING = "LineString"
private const val MULTILINESTRING = "MultiLineString"
private const val POLYGON = "Polygon"
private const val MULTIPOLYGON = "MultiPolygon"
private fun isGeometry(type: String): Boolean {
return type.matches(
(
POINT +
"|" +
MULTIPOINT +
"|" +
LINESTRING +
"|" +
MULTILINESTRING +
"|" +
POLYGON +
"|" +
MULTIPOLYGON +
"|" +
GEOMETRY_COLLECTION
).toRegex()
)
}
/**
* Parses a single GeoJSON feature which contains a geometry and properties member both of
* which can be null. Also parses the bounding box and id members of the feature if they exist.
*
* @param geoJsonFeature feature to parse
* @return GeoJsonFeature object
*/
private fun parseFeature(geoJsonFeature: JSONObject): GeoJsonFeature? {
var id: String? = null
var boundingBox: LatLngBounds? = null
var geometry: Geometry<*>? = null
var properties = HashMap<String, String?>()
try {
if (geoJsonFeature.has(FEATURE_ID)) {
id = geoJsonFeature.getString(FEATURE_ID)
}
if (geoJsonFeature.has(BOUNDING_BOX)) {
boundingBox = parseBoundingBox(geoJsonFeature.getJSONArray(BOUNDING_BOX))
}
if (geoJsonFeature.has(FEATURE_GEOMETRY) && !geoJsonFeature.isNull(FEATURE_GEOMETRY)) {
geometry = parseGeometry(geoJsonFeature.getJSONObject(FEATURE_GEOMETRY))
}
if (geoJsonFeature.has(PROPERTIES) && !geoJsonFeature.isNull(PROPERTIES)) {
properties = parseProperties(geoJsonFeature.getJSONObject("properties"))
}
} catch (e: JSONException) {
Log.w(LOG_TAG, "Feature could not be successfully parsed $geoJsonFeature")
return null
}
return GeoJsonFeature(geometry, id, properties, boundingBox)
}
/**
* Parses a bounding box given as a JSONArray of 4 elements in the order of lowest values for
* all axes followed by highest values. Axes order of a bounding box follows the axes order of
* geometries.
*
* @param coordinates array of 4 coordinates
* @return LatLngBounds containing the coordinates of the bounding box
* @throws JSONException if the bounding box could not be parsed
*/
@Throws(JSONException::class)
private fun parseBoundingBox(coordinates: JSONArray): LatLngBounds {
// Lowest values for all axes
val southWestCorner = LatLng(coordinates.getDouble(1), coordinates.getDouble(0))
// Highest value for all axes
val northEastCorner = LatLng(coordinates.getDouble(3), coordinates.getDouble(2))
return LatLngBounds(southWestCorner, northEastCorner)
}
/**
* Parses a single GeoJSON geometry object containing a coordinates array or a geometries array
* if it has type GeometryCollection. FeatureCollections, styles, bounding boxes, and properties
* are not processed by this method. If you want to parse GeoJSON including FeatureCollections,
* styles, bounding boxes, and properties into an array of [GeoJsonFeature]s then
* instantiate [GeoJsonParser] and call [GeoJsonParser.getFeatures].
*
* @param geoJsonGeometry geometry object to parse
* @return Geometry object
*/
fun parseGeometry(geoJsonGeometry: JSONObject): Geometry<*>? {
return try {
val geometryType = geoJsonGeometry.getString("type")
val geometryArray: JSONArray
geometryArray = if (geometryType == GEOMETRY_COLLECTION) {
// GeometryCollection
geoJsonGeometry.getJSONArray(GEOMETRY_COLLECTION_ARRAY)
} else if (isGeometry(geometryType)) {
geoJsonGeometry.getJSONArray(GEOMETRY_COORDINATES_ARRAY)
} else {
// No geometries or coordinates array
return null
}
createGeometry(geometryType, geometryArray)
} catch (e: JSONException) {
null
}
}
/**
* Converts a Geometry object into a GeoJsonFeature object. A geometry object has no ID,
* properties or bounding box so it is set to null.
*
* @param geoJsonGeometry Geometry object to convert into a Feature object
* @return new Feature object
*/
private fun parseGeometryToFeature(geoJsonGeometry: JSONObject): GeoJsonFeature? {
val geometry = parseGeometry(geoJsonGeometry)
if (geometry != null) {
return GeoJsonFeature(geometry, null, HashMap(), null)
}
Log.w(LOG_TAG, "Geometry could not be parsed")
return null
}
/**
* Parses the properties of a GeoJSON feature into a hashmap
*
* @param properties GeoJSON properties member
* @return hashmap containing property values
* @throws JSONException if the properties could not be parsed
*/
@Throws(JSONException::class)
private fun parseProperties(properties: JSONObject): HashMap<String, String?> {
val propertiesMap = HashMap<String, String?>()
val propertyKeys: Iterator<*> = properties.keys()
while (propertyKeys.hasNext()) {
val key = propertyKeys.next() as String
propertiesMap[key] = if (properties.isNull(key)) null else properties.getString(key)
}
return propertiesMap
}
/**
* Creates a Geometry object from the given type of geometry and its coordinates or
* geometries array
*
* @param geometryType type of geometry
* @param geometryArray coordinates or geometries of the geometry
* @return Geometry object
* @throws JSONException if the coordinates or geometries could be parsed
*/
@Throws(JSONException::class)
private fun createGeometry(geometryType: String, geometryArray: JSONArray): Geometry<*>? {
when (geometryType) {
POINT -> return createPoint(geometryArray)
MULTIPOINT -> return createMultiPoint(geometryArray)
LINESTRING -> return createLineString(geometryArray)
MULTILINESTRING -> return createMultiLineString(geometryArray)
POLYGON -> return createPolygon(geometryArray)
MULTIPOLYGON -> return createMultiPolygon(geometryArray)
GEOMETRY_COLLECTION -> return createGeometryCollection(geometryArray)
}
return null
}
/**
* Creates a new GeoJsonPoint object
*
* @param coordinates array containing the coordinates for the GeoJsonPoint
* @return GeoJsonPoint object
* @throws JSONException if coordinates cannot be parsed
*/
@Throws(JSONException::class)
private fun createPoint(coordinates: JSONArray): GeoJsonPoint {
val latLngAlt = parseCoordinate(coordinates)
return GeoJsonPoint(latLngAlt.latLng)
}
/**
* Creates a new GeoJsonMultiPoint object containing an array of GeoJsonPoint objects
*
* @param coordinates array containing the coordinates for the GeoJsonMultiPoint
* @return GeoJsonMultiPoint object
* @throws JSONException if coordinates cannot be parsed
*/
@Throws(JSONException::class)
private fun createMultiPoint(coordinates: JSONArray): GeoJsonMultiPoint {
val geoJsonPoints = mutableListOf<GeoJsonPoint>()
for (i in 0 until coordinates.length()) {
geoJsonPoints.add(createPoint(coordinates.getJSONArray(i)))
}
return GeoJsonMultiPoint(geoJsonPoints)
}
/**
* Creates a new GeoJsonLineString object
*
* @param coordinates array containing the coordinates for the GeoJsonLineString
* @return GeoJsonLineString object
* @throws JSONException if coordinates cannot be parsed
*/
@Throws(JSONException::class)
private fun createLineString(coordinates: JSONArray): GeoJsonLineString {
val latLngAlts = parseCoordinatesArray(coordinates)
val latLngs = mutableListOf<LatLng>()
for (latLngAlt in latLngAlts) {
latLngs.add(latLngAlt.latLng)
}
return GeoJsonLineString(latLngs)
}
/**
* Creates a new GeoJsonMultiLineString object containing an array of GeoJsonLineString objects
*
* @param coordinates array containing the coordinates for the GeoJsonMultiLineString
* @return GeoJsonMultiLineString object
* @throws JSONException if coordinates cannot be parsed
*/
@Throws(JSONException::class)
private fun createMultiLineString(coordinates: JSONArray): GeoJsonMultiLineString {
val geoJsonLineStrings = mutableListOf<GeoJsonLineString>()
for (i in 0 until coordinates.length()) {
geoJsonLineStrings.add(createLineString(coordinates.getJSONArray(i)))
}
return GeoJsonMultiLineString(geoJsonLineStrings)
}
/**
* Creates a new GeoJsonPolygon object
*
* @param coordinates array containing the coordinates for the GeoJsonPolygon
* @return GeoJsonPolygon object
* @throws JSONException if coordinates cannot be parsed
*/
@Throws(JSONException::class)
private fun createPolygon(coordinates: JSONArray): GeoJsonPolygon {
return GeoJsonPolygon(parseCoordinatesArrays(coordinates))
}
/**
* Creates a new GeoJsonMultiPolygon object containing an array of GeoJsonPolygon objects
*
* @param coordinates array containing the coordinates for the GeoJsonMultiPolygon
* @return GeoJsonPolygon object
* @throws JSONException if coordinates cannot be parsed
*/
@Throws(JSONException::class)
private fun createMultiPolygon(coordinates: JSONArray): GeoJsonMultiPolygon {
val geoJsonPolygons = mutableListOf<GeoJsonPolygon>()
for (i in 0 until coordinates.length()) {
geoJsonPolygons.add(createPolygon(coordinates.getJSONArray(i)))
}
return GeoJsonMultiPolygon(geoJsonPolygons)
}
/**
* Creates a new GeoJsonGeometryCollection object containing an array of Geometry
* objects
*
* @param geometries array containing the geometries for the GeoJsonGeometryCollection
* @return GeoJsonGeometryCollection object
* @throws JSONException if geometries cannot be parsed
*/
@Throws(JSONException::class)
private fun createGeometryCollection(geometries: JSONArray): GeoJsonGeometryCollection {
val geometryCollectionElements = mutableListOf<Geometry<*>>()
for (i in 0 until geometries.length()) {
val geometryElement = geometries.getJSONObject(i)
val geometry = parseGeometry(geometryElement)
if (geometry != null) {
// Do not add geometries that could not be parsed
geometryCollectionElements.add(geometry)
}
}
return GeoJsonGeometryCollection(geometryCollectionElements)
}
/**
* Parses an array containing a coordinate into a LatLngAlt object
*
* @param coordinates array containing the GeoJSON coordinate
* @return LatLngAlt object
* @throws JSONException if coordinate cannot be parsed
*/
@Throws(JSONException::class)
private fun parseCoordinate(coordinates: JSONArray): LatLngAlt {
// GeoJSON stores coordinates as Lng, Lat so we need to reverse
val latLng = LatLng(coordinates.getDouble(1), coordinates.getDouble(0))
val altitude = if (coordinates.length() < 3) null else coordinates.getDouble(2)
return LatLngAlt(latLng, altitude)
}
/**
* Parses an array containing coordinates into a List of LatLng objects
*
* @param coordinates array containing the GeoJSON coordinates
* @return List of LatLng objects
* @throws JSONException if coordinates cannot be parsed
*/
@Throws(JSONException::class)
private fun parseCoordinatesArray(coordinates: JSONArray): List<LatLngAlt> {
val coordinatesArray = mutableListOf<LatLngAlt>()
for (i in 0 until coordinates.length()) {
coordinatesArray.add(parseCoordinate(coordinates.getJSONArray(i)))
}
return coordinatesArray
}
/**
* Parses an array of arrays containing coordinates into a List of a List of LatLng
* objects
*
* @param coordinates array of an array containing the GeoJSON coordinates
* @return List of a List of LatLng objects
* @throws JSONException if coordinates cannot be parsed
*/
@Throws(JSONException::class)
private fun parseCoordinatesArrays(coordinates: JSONArray): List<List<LatLng>> {
val coordinatesArray = mutableListOf<MutableList<LatLng>>()
for (i in 0 until coordinates.length()) {
val latLngAlts = parseCoordinatesArray(coordinates.getJSONArray(i))
// this method is called for polygons, which do not have altitude values
val latLngs = mutableListOf<LatLng>()
for (latLngAlt in latLngAlts) {
latLngs.add(latLngAlt.latLng)
}
coordinatesArray.add(latLngs)
}
return coordinatesArray
}
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright 2020 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data.geojson
import com.google.maps.android.data.Point
import com.google.maps.android.model.LatLng
/**
* A GeoJsonPoint geometry contains a single [LatLng].
* @param coordinates coordinates of the KmlPoint
*/
class GeoJsonPoint(
val coordinates: LatLng,
) : Point(coordinates) {
/**
* Gets the type of geometry. The type of geometry conforms to the GeoJSON 'type'
* specification.
*
* @return type of geometry
*/
val type: String
get() = geometryType
}

View file

@ -0,0 +1,82 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data.geojson
import com.google.maps.android.data.DataPolygon
import com.google.maps.android.model.LatLng
/**
* A GeoJsonPolygon geometry contains an array of arrays of [LatLng]s.
* The first array is the polygon exterior boundary. Subsequent arrays are holes.
*/
class GeoJsonPolygon(
/**
* list of a list of coordinates of the GeoJsonPolygons
*/
val coordinates: List<List<LatLng>>,
) : DataPolygon<Any?> {
/**
* Gets the stored geometry object
*
* @return geometry object
*/
override val geometryObject: Any
get() = coordinates
/**
* Gets the type of geometry
*
* @return type of geometry
*/
override val geometryType: String
get() = TYPE
/**
* Gets an array of outer boundary coordinates
*
* @return array of outer boundary coordinates
*/
override val outerBoundaryCoordinates: List<LatLng?>
get() = // First array of coordinates are the outline
coordinates[POLYGON_OUTER_COORDINATE_INDEX] as MutableList<LatLng>
/**
* Gets an array of arrays of inner boundary coordinates
*
* @return array of arrays of inner boundary coordinates
*/
override val innerBoundaryCoordinates: List<List<LatLng?>?>
get() {
// Following arrays are holes
val innerBoundary = mutableListOf<MutableList<LatLng>>()
for (i in POLYGON_INNER_COORDINATE_INDEX until coordinates.size) {
innerBoundary.add(coordinates[i] as MutableList<LatLng>)
}
return innerBoundary
}
override fun toString(): String {
return """$TYPE{ coordinates=$coordinates}"""
}
companion object {
const val TYPE = "Polygon"
private const val POLYGON_OUTER_COORDINATE_INDEX = 0
private const val POLYGON_INNER_COORDINATE_INDEX = 1
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright 2020 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.maps.android.data.geojson
/**
* Class used to apply styles for the
* [GeoJsonFeature] objects
*/
internal interface GeoJsonStyle {
/**
* Gets the type of geometries this style can be applied to
*
* @return type of geometries this style can be applied to
*/
val geometryType: Array<String?>
}

View file

@ -0,0 +1,83 @@
package com.google.maps.android.model
import com.google.maps.android.SphericalUtil
import java.text.ParseException
import kotlin.math.max
import kotlin.math.min
class LatLng(
latitude: Double,
longitude: Double,
) {
val latitude: Double
val longitude: Double
init {
this.latitude = max(-90.0, min(90.0, latitude))
this.longitude = max(-180.0, min(180.0, longitude))
}
/**
* Returns the key of the nearest location from a predefined map of locations
*
* @param locationMap map of locations { key (unique identifier) => LatLng }
* @param limit furthest allowed match in meters, null if no limit
*/
fun getNearestLocation(
locationMap: Map<String, LatLng>?,
limit: Double? = null,
): String? {
var distance: Double
var nearestDistance = Double.POSITIVE_INFINITY
var nearestLocation: String? = null
locationMap?.keys?.forEach { key ->
locationMap[key]?.let {
distance = SphericalUtil.computeDistanceBetween(this, locationMap[key]!!)
if (distance < nearestDistance) {
if (limit == null || distance <= limit) {
nearestDistance = distance
nearestLocation = key
}
}
}
}
return nearestLocation
}
override fun equals(other: Any?): Boolean {
return if (other is LatLng) {
latitude == other.latitude && longitude == other.longitude
} else {
false
}
}
override fun hashCode(): Int {
var result = latitude.hashCode()
result = 31 * result + longitude.hashCode()
return result
}
override fun toString(): String {
return "$latitude,$longitude"
}
companion object {
@Throws(ParseException::class)
fun parse(value: String): LatLng {
val coordArr = value.split(",")
if (coordArr.size != 2) {
throw ParseException("Failed parsing '$value' as LatLng", 0)
}
val lon = coordArr[0].trim().toDoubleOrNull()
val lat = coordArr[1].trim().toDoubleOrNull()
if (lon == null || lat == null || (lon == 0.0 && lat == 0.0)) {
throw ParseException("Failed parsing '$value' as LatLng", 0)
}
return LatLng(lon, lat)
}
}
}

View file

@ -0,0 +1,37 @@
package com.google.maps.android.model
data class LatLngBounds(
val southwest: LatLng,
val northeast: LatLng,
) {
fun contains(point: LatLng): Boolean {
if (southwest.latitude <= point.latitude) {
if (point.latitude <= northeast.latitude) {
return if (southwest.longitude <= northeast.longitude) {
point.longitude in southwest.longitude..northeast.longitude
} else if (southwest.longitude <= point.longitude || point.longitude <= northeast.longitude) {
true
} else {
false
}
}
return false
}
return false
}
companion object {
fun parse(
west: Double,
south: Double,
east: Double,
north: Double,
): LatLngBounds {
return LatLngBounds(
southwest = LatLng(south, west),
northeast = LatLng(north, east)
)
}
}
}

View file

@ -0,0 +1,44 @@
/*
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package com.google.maps.android
import com.google.maps.android.model.LatLng
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class EncodedPolylineUtilTest {
@Test
fun decode() = runTest {
val encoded = "_p~iF~ps|U_ulLnnqC_mqNvxq`@"
val decodedLatLng = EncodedPolylineUtil.decode(encoded)
decodedLatLng.size shouldBe 3
decodedLatLng[0] shouldBe LatLng(38.5, -120.2)
decodedLatLng[1] shouldBe LatLng(40.7, -120.95)
decodedLatLng[2] shouldBe LatLng(43.252, -126.453)
/*
decodedLatLng[0].latitude shouldBe 38.5
decodedLatLng[0].longitude shouldBe -120.2
decodedLatLng[1].latitude shouldBe 40.7
decodedLatLng[1].longitude shouldBe -120.95
decodedLatLng[2].latitude shouldBe 43.252
decodedLatLng[2].longitude shouldBe -126.453
*/
}
}