diff --git a/library/src/main/java/com/google/maps/android/PolyUtil.kt b/library/src/main/java/com/google/maps/android/PolyUtil.kt index d6e3cd5e6..450ee78bf 100644 --- a/library/src/main/java/com/google/maps/android/PolyUtil.kt +++ b/library/src/main/java/com/google/maps/android/PolyUtil.kt @@ -17,6 +17,8 @@ package com.google.maps.android import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.data.Polygon +import com.google.maps.android.data.Polyline import com.google.maps.android.MathUtil.clamp import com.google.maps.android.MathUtil.hav import com.google.maps.android.MathUtil.havDistance @@ -62,7 +64,7 @@ object PolyUtil { * @return `true` if the point is inside the polygon, `false` otherwise. */ @JvmStatic - fun containsLocation(point: LatLng, polygon: List, geodesic: Boolean): Boolean { + fun containsLocation(point: LatLng, polygon: Polygon, geodesic: Boolean): Boolean { return containsLocation(point.latitude, point.longitude, polygon, geodesic) } @@ -74,7 +76,7 @@ object PolyUtil { fun containsLocation( latitude: Double, longitude: Double, - polygon: List, + polygon: Polygon, geodesic: Boolean ): Boolean { if (polygon.isEmpty()) { @@ -122,7 +124,7 @@ object PolyUtil { @JvmOverloads fun isLocationOnEdge( point: LatLng, - polygon: List, + polygon: Polygon, geodesic: Boolean, tolerance: Double = DEFAULT_TOLERANCE ): Boolean { @@ -145,7 +147,7 @@ object PolyUtil { @JvmOverloads fun isLocationOnPath( point: LatLng, - polyline: List, + polyline: Polyline, geodesic: Boolean, tolerance: Double = DEFAULT_TOLERANCE ): Boolean { @@ -154,12 +156,12 @@ object PolyUtil { private fun isLocationOnEdgeOrPath( point: LatLng, - poly: List, + polyline: Polyline, closed: Boolean, geodesic: Boolean, toleranceEarth: Double ): Boolean { - val idx = locationIndexOnEdgeOrPath(point, poly, closed, geodesic, toleranceEarth) + val idx = locationIndexOnEdgeOrPath(point, polyline, closed, geodesic, toleranceEarth) return (idx >= 0) } @@ -183,7 +185,7 @@ object PolyUtil { @JvmOverloads fun locationIndexOnPath( point: LatLng, - poly: List, + poly: Polyline, geodesic: Boolean, tolerance: Double = DEFAULT_TOLERANCE ): Int { @@ -209,7 +211,7 @@ object PolyUtil { @JvmStatic fun locationIndexOnEdgeOrPath( point: LatLng, - poly: List, + poly: Polyline, closed: Boolean, geodesic: Boolean, toleranceEarth: Double @@ -300,7 +302,7 @@ object PolyUtil { * @return a simplified poly produced by the Douglas-Peucker algorithm */ @JvmStatic - fun simplify(poly: List, tolerance: Double): List { + fun simplify(poly: Polyline, tolerance: Double): Polyline { require(poly.isNotEmpty()) { "Polyline must have at least 1 point" } require(tolerance > 0) { "Tolerance must be greater than zero" } @@ -339,13 +341,13 @@ object PolyUtil { * If this point is farther than the specified tolerance, it is kept, and the algorithm is * applied recursively to the two new segments. * - * @param poly The polyline to be simplified. + * @param polyline The polyline to be simplified. * @param tolerance The tolerance in meters. * @return A boolean array where `true` indicates that the point at the corresponding index * should be kept in the simplified polyline. */ - private fun douglasPeucker(poly: List, tolerance: Double): BooleanArray { - val n = poly.size + private fun douglasPeucker(polyline: Polyline, tolerance: Double): BooleanArray { + val n = polyline.size // We start with a boolean array that will mark the points to keep. // Initially, only the first and last points are marked for keeping. val keepPoint = BooleanArray(n) { false } @@ -368,7 +370,7 @@ object PolyUtil { // For the current segment, we find the point that is farthest from the line // connecting the start and end points. for (idx in start + 1 until end) { - val dist = distanceToLine(poly[idx], poly[start], poly[end]) + val dist = distanceToLine(polyline[idx], polyline[start], polyline[end]) if (dist > maxDist) { maxDist = dist maxIdx = idx @@ -393,13 +395,13 @@ object PolyUtil { * Returns true if the provided list of points is a closed polygon (i.e., the first and last * points are the same), and false if it is not * - * @param poly polyline or polygon + * @param polyline polyline or polygon * @return true if the provided list of points is a closed polygon (i.e., the first and last * points are the same), and false if it is not */ @JvmStatic - fun isClosedPolygon(poly: List): Boolean { - return poly.isNotEmpty() && poly.first() == poly.last() + fun isClosedPolygon(polyline: Polyline): Boolean { + return polyline.isNotEmpty() && polyline.first() == polyline.last() } /** @@ -447,7 +449,7 @@ object PolyUtil { * Decodes an encoded path string into a sequence of LatLngs. */ @JvmStatic - fun decode(encodedPath: String): List { + fun decode(encodedPath: String): Polyline { val len = encodedPath.length val path = mutableListOf() var index = 0 @@ -484,7 +486,7 @@ object PolyUtil { * Encodes a sequence of LatLngs into an encoded path string. */ @JvmStatic - fun encode(path: List): String { + fun encode(path: Polyline): String { var lastLat: Long = 0 var lastLng: Long = 0 val result = StringBuilder() diff --git a/library/src/main/java/com/google/maps/android/SphericalUtil.kt b/library/src/main/java/com/google/maps/android/SphericalUtil.kt index 0557b47de..2bb6c3867 100644 --- a/library/src/main/java/com/google/maps/android/SphericalUtil.kt +++ b/library/src/main/java/com/google/maps/android/SphericalUtil.kt @@ -17,6 +17,8 @@ package com.google.maps.android import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.data.Polygon +import com.google.maps.android.data.Polyline import com.google.maps.android.MathUtil.EARTH_RADIUS import com.google.maps.android.MathUtil.arcHav import com.google.maps.android.MathUtil.havDistance @@ -40,16 +42,18 @@ object SphericalUtil { @JvmStatic fun computeHeading(from: LatLng, to: LatLng): Double { // http://williams.best.vwh.net/avform.htm#Crs - val fromLat = Math.toRadians(from.latitude) - val fromLng = Math.toRadians(from.longitude) - val toLat = Math.toRadians(to.latitude) - val toLng = Math.toRadians(to.longitude) - val dLng = toLng - fromLng - val heading = atan2( - sin(dLng) * cos(toLat), - cos(fromLat) * sin(toLat) - sin(fromLat) * cos(toLat) * cos(dLng) - ) - return wrap(Math.toDegrees(heading), -180.0, 180.0) + val fromLatRad = from.latitude.toRadians() + val toLatRad = to.latitude.toRadians() + val deltaLngRad = (to.longitude - from.longitude).toRadians() + + // Breaking the formula down into Y and X components for atan2(). + val y = sin(deltaLngRad) * cos(toLatRad) + val x = cos(fromLatRad) * sin(toLatRad) - + sin(fromLatRad) * cos(toLatRad) * cos(deltaLngRad) + + val headingRad = atan2(y, x) + + return wrap(headingRad.toDegrees(), -180.0, 180.0) } /** @@ -62,23 +66,26 @@ object SphericalUtil { */ @JvmStatic fun computeOffset(from: LatLng, distance: Double, heading: Double): LatLng { - var distance = distance - var heading = heading - distance /= EARTH_RADIUS - heading = Math.toRadians(heading) - // http://williams.best.vwh.net/avform.htm#LL - val fromLat = Math.toRadians(from.latitude) - val fromLng = Math.toRadians(from.longitude) - val cosDistance = cos(distance) - val sinDistance = sin(distance) - val sinFromLat = sin(fromLat) - val cosFromLat = cos(fromLat) - val sinLat = cosDistance * sinFromLat + sinDistance * cosFromLat * cos(heading) - val dLng = atan2( - sinDistance * cosFromLat * sin(heading), - cosDistance - sinFromLat * sinLat - ) - return LatLng(Math.toDegrees(asin(sinLat)), Math.toDegrees(fromLng + dLng)) + val distanceRad = distance / EARTH_RADIUS + val headingRad = heading.toRadians() + + val (fromLatRad, fromLngRad) = from.toRadians() + + val cosDistance = cos(distanceRad) + val sinDistance = sin(distanceRad) + val sinFromLat = sin(fromLatRad) + val cosFromLat = cos(fromLatRad) + + val sinToLat = cosDistance * sinFromLat + sinDistance * cosFromLat * cos(headingRad) + val toLatRad = asin(sinToLat) + + val y = sin(headingRad) * sinDistance * cosFromLat + val x = cosDistance - sinFromLat * sinToLat + val dLngRad = atan2(y, x) + + val toLngRad = fromLngRad + dLngRad + + return LatLng(toLatRad.toDegrees(), toLngRad.toDegrees()) } /** @@ -93,15 +100,13 @@ object SphericalUtil { */ @JvmStatic fun computeOffsetOrigin(to: LatLng, distance: Double, heading: Double): LatLng? { - var distance = distance - var heading = heading - heading = Math.toRadians(heading) - distance /= EARTH_RADIUS + val headingRad = heading.toRadians() + val distanceRad = distance / EARTH_RADIUS // http://lists.maptools.org/pipermail/proj/2008-October/003939.html - val n1 = cos(distance) - val n2 = sin(distance) * cos(heading) - val n3 = sin(distance) * sin(heading) - val n4 = sin(Math.toRadians(to.latitude)) + val n1 = cos(distanceRad) + val n2 = sin(distanceRad) * cos(headingRad) + val n3 = sin(distanceRad) * sin(headingRad) + val n4 = sin(to.latitude.toRadians()) // There are two solutions for b. b = n2 * n4 +/- sqrt(), one solution results // in the latitude outside the [-90, 90] range. We first try one solution and // back off to the other if we are outside that range. @@ -124,9 +129,9 @@ object SphericalUtil { // No solution which would make sense in LatLng-space. return null } - val fromLngRadians = Math.toRadians(to.longitude) - + val fromLngRadians = to.longitude.toRadians() - atan2(n3, n1 * cos(fromLatRadians) - n2 * sin(fromLatRadians)) - return LatLng(Math.toDegrees(fromLatRadians), Math.toDegrees(fromLngRadians)) + return LatLng(fromLatRadians.toDegrees(), fromLngRadians.toDegrees()) } /** @@ -141,17 +146,17 @@ object SphericalUtil { @JvmStatic fun interpolate(from: LatLng, to: LatLng, fraction: Double): LatLng { // http://en.wikipedia.org/wiki/Slerp - val fromLat = Math.toRadians(from.latitude) - val fromLng = Math.toRadians(from.longitude) - val toLat = Math.toRadians(to.latitude) - val toLng = Math.toRadians(to.longitude) - val cosFromLat = cos(fromLat) - val cosToLat = cos(toLat) + val (fromLatRad, fromLngRad) = from.toRadians() + val (toLatRad, toLngRad) = to.toRadians() + + val cosFromLat = cos(fromLatRad) + val cosToLat = cos(toLatRad) // Computes Spherical interpolation coefficients. val angle = computeAngleBetween(from, to) val sinAngle = sin(angle) if (sinAngle < 1E-6) { + // Fall back to linear interpolation for very small angles. return LatLng( from.latitude + fraction * (to.latitude - from.latitude), from.longitude + fraction * (to.longitude - from.longitude) @@ -161,64 +166,58 @@ object SphericalUtil { val b = sin(fraction * angle) / sinAngle // Converts from polar to vector and interpolate. - val x = a * cosFromLat * cos(fromLng) + b * cosToLat * cos(toLng) - val y = a * cosFromLat * sin(fromLng) + b * cosToLat * sin(toLng) - val z = a * sin(fromLat) + b * sin(toLat) + val x = a * cosFromLat * cos(fromLngRad) + b * cosToLat * cos(toLngRad) + val y = a * cosFromLat * sin(fromLngRad) + b * cosToLat * sin(toLngRad) + val z = a * sin(fromLatRad) + b * sin(toLatRad) // Converts interpolated vector back to polar. - val lat = atan2(z, sqrt(x * x + y * y)) - val lng = atan2(y, x) - return LatLng(Math.toDegrees(lat), Math.toDegrees(lng)) + val latRad = atan2(z, sqrt(x * x + y * y)) + val lngRad = atan2(y, x) + + return LatLng(latRad.toDegrees(), lngRad.toDegrees()) } /** * 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)) - } + private fun distanceRadians(lat1: Double, lng1: Double, lat2: Double, lng2: Double) = + arcHav(havDistance(lat1, lat2, lng1 - lng2)) /** - * Returns the angle between two LatLngs, in radians. This is the same as the distance + * Returns the angle between two [LatLng]s, in radians. This is the same as the distance * on the unit sphere. */ @JvmStatic - 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) - ) - } + fun computeAngleBetween(from: LatLng, to: LatLng) = distanceRadians( + from.latitude.toRadians(), from.longitude.toRadians(), + to.latitude.toRadians(), to.longitude.toRadians() + ) /** - * Returns the distance between two LatLngs, in meters. + * Returns the distance between two [LatLng]s, in meters. */ @JvmStatic - fun computeDistanceBetween(from: LatLng, to: LatLng): Double { - return computeAngleBetween(from, to) * EARTH_RADIUS - } + fun computeDistanceBetween(from: LatLng, to: LatLng) = + computeAngleBetween(from, to) * EARTH_RADIUS /** * Returns the length of the given path, in meters, on Earth. */ @JvmStatic - fun computeLength(path: List): Double { + fun computeLength(path: Polyline): Double { if (path.size < 2) { return 0.0 } - var length = 0.0 - var prev: LatLng? = null - for (point in path) { - if (prev != null) { - val prevLat = Math.toRadians(prev.latitude) - val prevLng = Math.toRadians(prev.longitude) - val lat = Math.toRadians(point.latitude) - val lng = Math.toRadians(point.longitude) - length += distanceRadians(prevLat, prevLng, lat, lng) - } - prev = point + + // Using zipWithNext() is a more functional and idiomatic way to handle + // adjacent pairs in a collection. We then sum the distances between each pair. + val totalDistance = path.zipWithNext().sumOf { (prev, point) -> + val (prevLatRad, prevLngRad) = prev.toRadians() + val (latRad, lngRad) = point.toRadians() + distanceRadians(prevLatRad, prevLngRad, latRad, lngRad) } - return length * EARTH_RADIUS + + return totalDistance * EARTH_RADIUS } /** @@ -228,9 +227,7 @@ object SphericalUtil { * @return The path's area in square meters. */ @JvmStatic - fun computeArea(path: List): Double { - return abs(computeSignedArea(path)) - } + fun computeArea(path: Polygon) = abs(computeSignedArea(path)) /** * Returns the signed area of a closed path on Earth. The sign of the area may be used to @@ -241,9 +238,7 @@ object SphericalUtil { * @return The loop's area in square meters. */ @JvmStatic - fun computeSignedArea(path: List): Double { - return computeSignedArea(path, EARTH_RADIUS) - } + fun computeSignedArea(path: Polygon) = computeSignedArea(path, EARTH_RADIUS) /** * Returns the signed area of a closed path on a sphere of given radius. @@ -252,24 +247,27 @@ object SphericalUtil { */ @JvmStatic fun computeSignedArea(path: List, radius: Double): Double { - val size = path.size - if (size < 3) { + if (path.size < 3) { return 0.0 } - var total = 0.0 - val prev = path[size - 1] - var prevTanLat = tan((PI / 2 - Math.toRadians(prev.latitude)) / 2) - var prevLng = Math.toRadians(prev.longitude) - // For each edge, accumulate the signed area of the triangle formed by the North Pole - // and that edge ("polar triangle"). - for (point in path) { - val tanLat = tan((PI / 2 - Math.toRadians(point.latitude)) / 2) - val lng = Math.toRadians(point.longitude) - total += polarTriangleArea(tanLat, lng, prevTanLat, prevLng) - prevTanLat = tanLat - prevLng = lng + + // This local function to keep the logic clean + fun polarArea(p1: LatLng, p2: LatLng): Double { + val tanLat1 = tan((PI / 2 - p1.latitude.toRadians()) / 2) + val tanLat2 = tan((PI / 2 - p2.latitude.toRadians()) / 2) + val lng1 = p1.longitude.toRadians() + val lng2 = p2.longitude.toRadians() + return polarTriangleArea(tanLat1, lng2, tanLat2, lng1) } - return total * (radius * radius) + + // Create a sequence of edges, including the final edge that closes the polygon. + // Using a sequence avoids creating an intermediate list. + val edges = path.asSequence().zipWithNext() + (path.last() to path.first()) + + // Use a sequence to avoid creating intermediate lists + val totalArea = edges.sumOf { (p1, p2) -> polarArea(p1, p2) } + + return totalArea * (radius * radius) } /** @@ -284,4 +282,12 @@ object SphericalUtil { val t = tan1 * tan2 return 2 * atan2(t * sin(deltaLng), 1 + t * cos(deltaLng)) } -} \ No newline at end of file +} + +/** + * Helper extension function to convert a LatLng to a pair of radians. + */ +private fun LatLng.toRadians() = Pair(latitude.toRadians(), longitude.toRadians()) + +private fun Double.toRadians() = this * (PI / 180.0) +private fun Double.toDegrees() = this * (180.0 / PI) diff --git a/library/src/main/java/com/google/maps/android/data/DataPolygon.java b/library/src/main/java/com/google/maps/android/data/DataPolygon.kt similarity index 54% rename from library/src/main/java/com/google/maps/android/data/DataPolygon.java rename to library/src/main/java/com/google/maps/android/data/DataPolygon.kt index 3f256845b..6fe48dff9 100644 --- a/library/src/main/java/com/google/maps/android/data/DataPolygon.java +++ b/library/src/main/java/com/google/maps/android/data/DataPolygon.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,34 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.google.maps.android.data -package com.google.maps.android.data; - -import com.google.android.gms.maps.model.LatLng; - -import java.util.List; +import com.google.android.gms.maps.model.LatLng /** * An interface containing the common properties of - * {@link com.google.maps.android.data.geojson.GeoJsonPolygon GeoJsonPolygon} and - * {@link com.google.maps.android.data.kml.KmlPolygon KmlPolygon} + * [com.google.maps.android.data.geojson.GeoJsonPolygon] and + * [com.google.maps.android.data.kml.KmlPolygon] * - * @param the type of Polygon - GeoJsonPolygon or KmlPolygon + * @param T the type of Polygon - GeoJsonPolygon or KmlPolygon */ -public interface DataPolygon extends Geometry { - +interface DataPolygon : Geometry { /** * Gets an array of outer boundary coordinates - * - * @return array of outer boundary coordinates */ - List getOuterBoundaryCoordinates(); + val outerBoundaryCoordinates: Polygon /** * Gets an array of arrays of inner boundary coordinates - * - * @return array of arrays of inner boundary coordinates */ - List> getInnerBoundaryCoordinates(); - -} + val innerBoundaryCoordinates: List +} \ No newline at end of file diff --git a/library/src/main/java/com/google/maps/android/data/Feature.java b/library/src/main/java/com/google/maps/android/data/Feature.java deleted file mode 100644 index 5e670bb45..000000000 --- a/library/src/main/java/com/google/maps/android/data/Feature.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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 java.util.HashMap; -import java.util.Map; -import java.util.Observable; - -/** - * An abstraction that shares the common properties of - * {@link com.google.maps.android.data.kml.KmlPlacemark KmlPlacemark} and - * {@link com.google.maps.android.data.geojson.GeoJsonFeature GeoJsonFeature} - */ -public class Feature extends Observable { - - protected String mId; - - private final Map mProperties; - - private Geometry mGeometry; - - /** - * Creates a new Feature object - * - * @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 - */ - public Feature(Geometry featureGeometry, String id, - Map properties) { - mGeometry = featureGeometry; - mId = id; - if (properties == null) { - mProperties = new HashMap<>(); - } else { - mProperties = properties; - } - } - - /** - * Returns all the stored property keys - * - * @return iterable of property keys - */ - public Iterable getPropertyKeys() { - return mProperties.keySet(); - } - - /** - * Gets the property entry set - * - * @return property entry set - */ - public Iterable getProperties() { - return mProperties.entrySet(); - } - - /** - * Gets the value for a stored property - * - * @param property key of the property - * @return value of the property if its key exists, otherwise null - */ - public String getProperty(String property) { - return mProperties.get(property); - } - - /** - * Gets the id of the feature - * - * @return id - */ - public String getId() { - return mId; - } - - /** - * Checks whether the given property key exists - * - * @param property key of the property to check - * @return true if property key exists, false otherwise - */ - public boolean hasProperty(String property) { - return mProperties.containsKey(property); - } - - /** - * Gets the geometry object - * - * @return geometry object - */ - public Geometry getGeometry() { - return mGeometry; - } - - /** - * Gets whether the placemark has properties - * - * @return true if there are properties in the properties map, false otherwise - */ - public boolean hasProperties() { - return mProperties.size() > 0; - } - - /** - * Checks if the geometry is assigned - * - * @return true if feature contains geometry object, otherwise null - */ - public boolean hasGeometry() { - return (mGeometry != 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 String setProperty(String property, String propertyValue) { - 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 String removeProperty(String property) { - return mProperties.remove(property); - } - - /** - * Sets the stored Geometry and redraws it on the layer if it has already been added - * - * @param geometry Geometry to set - */ - protected void setGeometry(Geometry geometry) { - mGeometry = geometry; - } -} diff --git a/library/src/main/java/com/google/maps/android/data/Feature.kt b/library/src/main/java/com/google/maps/android/data/Feature.kt new file mode 100644 index 000000000..dc4ea4f33 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/data/Feature.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2025 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.data + +import java.util.Observable + +/** + * An abstraction that shares the common properties of + * [com.google.maps.android.data.kml.KmlPlacemark] and + * [com.google.maps.android.data.geojson.GeoJsonFeature] + */ +open class Feature( + geometry: Geometry<*>?, + id: String?, + properties: Map? +) : Observable() { + open var id: String? = id + protected set + + private val _properties: MutableMap = properties?.toMutableMap() ?: mutableMapOf() + + open var geometry: Geometry<*>? = geometry + protected set(value) { + field = value + setChanged() + notifyObservers() + } + + /** + * Returns all the stored property keys + */ + val propertyKeys: Iterable + get() = _properties.keys + + /** + * Gets the property entry set + */ + val properties: Iterable> + get() = _properties.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? = _properties[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 = _properties.containsKey(property) + + /** + * Gets whether the placemark has properties + * + * @return true if there are properties in the properties map, false otherwise + */ + fun hasProperties(): Boolean = _properties.isNotEmpty() + + /** + * Checks if the geometry is assigned + * + * @return true if feature contains geometry object, otherwise null + */ + fun hasGeometry(): Boolean = 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? { + val prev = _properties.put(property, propertyValue) + setChanged() + notifyObservers() + return prev + } + + /** + * 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? { + val prev = _properties.remove(property) + setChanged() + notifyObservers() + return prev + } +} diff --git a/library/src/main/java/com/google/maps/android/data/Geometry.java b/library/src/main/java/com/google/maps/android/data/Geometry.kt similarity index 60% rename from library/src/main/java/com/google/maps/android/data/Geometry.java rename to library/src/main/java/com/google/maps/android/data/Geometry.kt index 857f97f98..7e628bf68 100644 --- a/library/src/main/java/com/google/maps/android/data/Geometry.java +++ b/library/src/main/java/com/google/maps/android/data/Geometry.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google Inc. + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,27 +13,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.google.maps.android.data -package com.google.maps.android.data; +import com.google.android.gms.maps.model.LatLng + +/** + * A Polyline is a list of LatLngs where each LatLng is a vertex of the line. + */ +typealias Polyline = List + +/** + * A Polygon is a list of LatLngs where each LatLng is a vertex of the polygon. + */ +typealias Polygon = List /** * An abstraction that represents a Geometry object * - * @param the type of Geometry object + * @param T the type of Geometry object */ -public interface Geometry { +interface Geometry { /** * Gets the type of geometry - * - * @return type of geometry */ - String getGeometryType(); + val geometryType: String /** * Gets the stored KML Geometry object - * - * @return geometry object */ - T getGeometryObject(); - -} + val geometryObject: T +} \ No newline at end of file diff --git a/library/src/main/java/com/google/maps/android/data/Layer.java b/library/src/main/java/com/google/maps/android/data/Layer.kt similarity index 53% rename from library/src/main/java/com/google/maps/android/data/Layer.java rename to library/src/main/java/com/google/maps/android/data/Layer.kt index 63d8b26fe..c71229fb0 100644 --- a/library/src/main/java/com/google/maps/android/data/Layer.java +++ b/library/src/main/java/com/google/maps/android/data/Layer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Google Inc. + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,59 +13,53 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.google.maps.android.data -package com.google.maps.android.data; - -import com.google.android.gms.maps.GoogleMap; -import com.google.maps.android.data.geojson.GeoJsonLineStringStyle; -import com.google.maps.android.data.geojson.GeoJsonPointStyle; -import com.google.maps.android.data.geojson.GeoJsonPolygonStyle; -import com.google.maps.android.data.geojson.GeoJsonRenderer; -import com.google.maps.android.data.kml.KmlContainer; -import com.google.maps.android.data.kml.KmlGroundOverlay; -import com.google.maps.android.data.kml.KmlRenderer; +import com.google.android.gms.maps.GoogleMap +import com.google.maps.android.data.geojson.GeoJsonLineStringStyle +import com.google.maps.android.data.geojson.GeoJsonPointStyle +import com.google.maps.android.data.geojson.GeoJsonPolygonStyle +import com.google.maps.android.data.geojson.GeoJsonRenderer +import com.google.maps.android.data.kml.KmlContainer +import com.google.maps.android.data.kml.KmlGroundOverlay +import com.google.maps.android.data.kml.KmlRenderer /** * An abstraction that shares the common properties of - * {@link com.google.maps.android.data.kml.KmlLayer KmlLayer} and - * {@link com.google.maps.android.data.geojson.GeoJsonLayer GeoJsonLayer} + * [com.google.maps.android.data.kml.KmlLayer] and + * [com.google.maps.android.data.geojson.GeoJsonLayer] */ -public abstract class Layer { - - private Renderer mRenderer; +abstract class Layer { + @JvmField + protected var renderer: Renderer? = null /** * Adds the KML data to the map */ - protected void addKMLToMap() { - if (mRenderer instanceof KmlRenderer) { - ((KmlRenderer) mRenderer).addLayerToMap(); - } else { - throw new UnsupportedOperationException("Stored renderer is not a KmlRenderer"); - } + protected fun addKMLToMap() { + val kmlRenderer = renderer as? KmlRenderer + ?: throw UnsupportedOperationException("Stored renderer is not a KmlRenderer") + kmlRenderer.addLayerToMap() } /** * Adds GeoJson data to the map */ - protected void addGeoJsonToMap() { - if (mRenderer instanceof GeoJsonRenderer) { - ((GeoJsonRenderer) mRenderer).addLayerToMap(); - } else { - throw new UnsupportedOperationException("Stored renderer is not a GeoJsonRenderer"); - } + protected fun addGeoJsonToMap() { + val geoJsonRenderer = renderer as? GeoJsonRenderer + ?: throw UnsupportedOperationException("Stored renderer is not a GeoJsonRenderer") + geoJsonRenderer.addLayerToMap() } - public abstract void addLayerToMap(); + abstract fun addLayerToMap() /** * Removes all the data from the map and clears all the stored placemarks */ - public void removeLayerFromMap() { - if (mRenderer instanceof GeoJsonRenderer) { - ((GeoJsonRenderer) mRenderer).removeLayerFromMap(); - } else if (mRenderer instanceof KmlRenderer) { - ((KmlRenderer) mRenderer).removeLayerFromMap(); + fun removeLayerFromMap() { + when (val r = renderer) { + is GeoJsonRenderer -> r.removeLayerFromMap() + is KmlRenderer -> r.removeLayerFromMap() } } @@ -73,22 +67,22 @@ public void removeLayerFromMap() { * Sets a single click listener for the entire GoogleMap object, that will be called * with the corresponding Feature object when an object on the map (Polygon, * Marker, Polyline) is clicked. - *

+ * * If getFeature() returns null this means that either the object is inside a KMLContainer, * or the object is a MultiPolygon, MultiLineString or MultiPoint and must * be handled differently. * * @param listener Listener providing the onFeatureClick method to call. */ - public void setOnFeatureClickListener(final OnFeatureClickListener listener) { - mRenderer.setOnFeatureClickListener(listener); + fun setOnFeatureClickListener(listener: OnFeatureClickListener) { + renderer?.setOnFeatureClickListener(listener) } /** * Callback interface for when a map object is clicked. */ - public interface OnFeatureClickListener { - void onFeatureClick(Feature feature); + fun interface OnFeatureClickListener { + fun onFeatureClick(feature: Feature) } /** @@ -96,8 +90,8 @@ public interface OnFeatureClickListener { * * @param renderer the new Renderer object that belongs to this Layer */ - protected void storeRenderer(Renderer renderer) { - mRenderer = renderer; + protected fun storeRenderer(renderer: Renderer) { + this.renderer = renderer } /** @@ -105,8 +99,8 @@ protected void storeRenderer(Renderer renderer) { * * @return iterable of Feature elements */ - public Iterable getFeatures() { - return mRenderer.getFeatures(); + open fun getFeatures(): Iterable { + return renderer?.features ?: emptyList() } /** @@ -117,12 +111,12 @@ public Iterable getFeatures() { * @param mapObject Object * @return Feature for the given object */ - public Feature getFeature(Object mapObject) { - return mRenderer.getFeature(mapObject); + fun getFeature(mapObject: Any): T? { + return renderer?.getFeature(mapObject) as T? } - public Feature getContainerFeature(Object mapObject) { - return mRenderer.getContainerFeature(mapObject); + fun getContainerFeature(mapObject: Any): T? { + return renderer?.getContainerFeature(mapObject) as T? } /** @@ -130,20 +124,15 @@ public Feature getContainerFeature(Object mapObject) { * * @return true if there are features on the layer, false otherwise */ - protected boolean hasFeatures() { - return mRenderer.hasFeatures(); - } + protected open fun hasFeatures(): Boolean = renderer?.hasFeatures() ?: false /** * Checks if the layer contains any KmlContainers * * @return true if there is at least 1 container within the KmlLayer, false otherwise */ - protected boolean hasContainers() { - if (mRenderer instanceof KmlRenderer) { - return ((KmlRenderer) mRenderer).hasNestedContainers(); - } - return false; + protected open fun hasContainers(): Boolean { + return (renderer as? KmlRenderer)?.hasNestedContainers() ?: false } /** @@ -151,11 +140,8 @@ protected boolean hasContainers() { * * @return iterable of KmlContainerInterface objects */ - protected Iterable getContainers() { - if (mRenderer instanceof KmlRenderer) { - return ((KmlRenderer) mRenderer).getNestedContainers(); - } - return null; + protected open fun getContainers(): Iterable? { + return (renderer as? KmlRenderer)?.nestedContainers } /** @@ -163,11 +149,8 @@ protected Iterable getContainers() { * * @return iterable of KmlGroundOverlay objects */ - protected Iterable getGroundOverlays() { - if (mRenderer instanceof KmlRenderer) { - return ((KmlRenderer) mRenderer).getGroundOverlays(); - } - return null; + protected open fun getGroundOverlays(): Iterable? { + return (renderer as? KmlRenderer)?.groundOverlays } /** @@ -175,9 +158,8 @@ protected Iterable getGroundOverlays() { * * @return map on which the layer is rendered */ - public GoogleMap getMap() { - return mRenderer.getMap(); - } + val map: GoogleMap? + get() = renderer?.map /** * Renders the layer on the given map. The layer on the current map is removed and @@ -185,8 +167,8 @@ public GoogleMap getMap() { * * @param map to render the layer on, if null the layer is cleared from the current map */ - public void setMap(GoogleMap map) { - mRenderer.setMap(map); + fun setMap(map: GoogleMap?) { + renderer?.map = map } /** @@ -194,17 +176,16 @@ public void setMap(GoogleMap map) { * * @return true if the layer is on the map, false otherwise */ - public boolean isLayerOnMap() { - return mRenderer.isLayerOnMap(); - } + val isLayerOnMap: Boolean + get() = renderer?.isLayerOnMap ?: false /** * Adds a provided feature to the map * * @param feature feature to add to map */ - protected void addFeature(Feature feature) { - mRenderer.addFeature(feature); + protected open fun addFeature(feature: T) { + renderer?.addFeature(feature) } /** @@ -212,8 +193,8 @@ protected void addFeature(Feature feature) { * * @param feature feature to be removed */ - protected void removeFeature(Feature feature) { - mRenderer.removeFeature(feature); + protected open fun removeFeature(feature: T) { + renderer?.removeFeature(feature) } /** @@ -222,9 +203,8 @@ protected void removeFeature(Feature feature) { * * @return default style used to render GeoJsonPoints */ - public GeoJsonPointStyle getDefaultPointStyle() { - return mRenderer.getDefaultPointStyle(); - } + val defaultPointStyle: GeoJsonPointStyle? + get() = renderer?.defaultPointStyle /** * Gets the default style used to render GeoJsonLineStrings. Any changes to this style will be @@ -232,9 +212,8 @@ public GeoJsonPointStyle getDefaultPointStyle() { * * @return default style used to render GeoJsonLineStrings */ - public GeoJsonLineStringStyle getDefaultLineStringStyle() { - return mRenderer.getDefaultLineStringStyle(); - } + val defaultLineStringStyle: GeoJsonLineStringStyle? + get() = renderer?.defaultLineStringStyle /** * Gets the default style used to render GeoJsonPolygons. Any changes to this style will be @@ -242,7 +221,6 @@ public GeoJsonLineStringStyle getDefaultLineStringStyle() { * * @return default style used to render GeoJsonPolygons */ - public GeoJsonPolygonStyle getDefaultPolygonStyle() { - return mRenderer.getDefaultPolygonStyle(); - } + val defaultPolygonStyle: GeoJsonPolygonStyle? + get() = renderer?.defaultPolygonStyle } diff --git a/library/src/main/java/com/google/maps/android/data/LineString.java b/library/src/main/java/com/google/maps/android/data/LineString.java deleted file mode 100644 index 42f414eb1..000000000 --- a/library/src/main/java/com/google/maps/android/data/LineString.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.android.gms.maps.model.LatLng; - -import java.util.List; - -import androidx.annotation.NonNull; - -/** - * An abstraction that shares the common properties of - * {@link com.google.maps.android.data.kml.KmlLineString KmlLineString} and - * {@link com.google.maps.android.data.geojson.GeoJsonLineString GeoJsonLineString} - */ -public class LineString implements Geometry> { - - private static final String GEOMETRY_TYPE = "LineString"; - - private final List mCoordinates; - - /** - * Creates a new LineString object - * - * @param coordinates array of coordinates - */ - public LineString(List coordinates) { - if (coordinates == null) { - throw new IllegalArgumentException("Coordinates cannot be null"); - } - mCoordinates = coordinates; - } - - /** - * Gets the type of geometry - * - * @return type of geometry - */ - public String getGeometryType() { - return GEOMETRY_TYPE; - } - - /** - * Gets the coordinates of the LineString - * - * @return coordinates of the LineString - */ - public List getGeometryObject() { - return mCoordinates; - } - - @NonNull - @Override - public String toString() { - StringBuilder sb = new StringBuilder(GEOMETRY_TYPE).append("{"); - sb.append("\n coordinates=").append(mCoordinates); - sb.append("\n}\n"); - return sb.toString(); - } - -} diff --git a/library/src/main/java/com/google/maps/android/data/LineString.kt b/library/src/main/java/com/google/maps/android/data/LineString.kt new file mode 100644 index 000000000..2f2f3c185 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/data/LineString.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2025 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.data + +import com.google.android.gms.maps.model.LatLng + +/** + * An abstraction that shares the common properties of + * [com.google.maps.android.data.kml.KmlLineString] and + * [com.google.maps.android.data.geojson.GeoJsonLineString] + */ +open class LineString(coordinates: Polyline) : Geometry { + + private val _coordinates: Polyline = coordinates + + /** + * Gets the coordinates of the LineString + */ + open val coordinates: Polyline + get() = _coordinates + + /** + * Gets the type of geometry + */ + override val geometryType: String = "LineString" + + /** + * Gets the geometry object + */ + override val geometryObject: Polyline = _coordinates + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as LineString + + return _coordinates == other._coordinates + } + + override fun hashCode(): Int { + return _coordinates.hashCode() + } + + override fun toString(): String { + return "LineString(coordinates=$_coordinates)" + } +} diff --git a/library/src/main/java/com/google/maps/android/data/MultiGeometry.java b/library/src/main/java/com/google/maps/android/data/MultiGeometry.java deleted file mode 100644 index d294eee4f..000000000 --- a/library/src/main/java/com/google/maps/android/data/MultiGeometry.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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 java.util.ArrayList; -import java.util.List; - -import androidx.annotation.NonNull; - -/** - * An abstraction that shares the common properties of - * {@link com.google.maps.android.data.kml.KmlMultiGeometry KmlMultiGeometry} - * and {@link com.google.maps.android.data.geojson.GeoJsonMultiLineString GeoJsonMultiLineString}, - * {@link com.google.maps.android.data.geojson.GeoJsonMultiPoint GeoJsonMultiPoint} and - * {@link com.google.maps.android.data.geojson.GeoJsonMultiPolygon GeoJsonMultiPolygon} - */ -public class MultiGeometry implements Geometry { - - private String geometryType = "MultiGeometry"; - - private List mGeometries; - - /** - * Creates a new MultiGeometry object - * - * @param geometries contains list of Polygons, Linestrings or Points - */ - public MultiGeometry(List geometries) { - if (geometries == null) { - throw new IllegalArgumentException("Geometries cannot be null"); - } - - //convert unknown geometry type (due to GeoJSON types) to Geometry type - ArrayList geometriesList = new ArrayList(); - for (Geometry geometry : geometries) { - geometriesList.add(geometry); - } - - mGeometries = geometriesList; - } - - /** - * Gets the type of geometry - * - * @return type of geometry - */ - public String getGeometryType() { - return geometryType; - } - - /** - * Gets the stored geometry object - * - * @return geometry object - */ - public List getGeometryObject() { - return mGeometries; - } - - /** - * Set the type of geometry - * - * @param type String describing type of geometry - */ - public void setGeometryType(String type) { - geometryType = type; - } - - @NonNull - @Override - public String toString() { - String typeString = "Geometries="; - if (geometryType.equals("MultiPoint")) { - typeString = "LineStrings="; - } - if (geometryType.equals("MultiLineString")) { - typeString = "points="; - } - if (geometryType.equals("MultiPolygon")) { - typeString = "Polygons="; - } - - StringBuilder sb = new StringBuilder(getGeometryType()).append("{"); - sb.append("\n " + typeString).append(getGeometryObject()); - sb.append("\n}\n"); - return sb.toString(); - } -} diff --git a/library/src/main/java/com/google/maps/android/data/MultiGeometry.kt b/library/src/main/java/com/google/maps/android/data/MultiGeometry.kt new file mode 100644 index 000000000..71abb5617 --- /dev/null +++ b/library/src/main/java/com/google/maps/android/data/MultiGeometry.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2025 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.data + +/** + * An abstraction that shares the common properties of + * [com.google.maps.android.data.kml.KmlMultiGeometry] and + * [com.google.maps.android.data.geojson.GeoJsonMultiPoint], + * [com.google.maps.android.data.geojson.GeoJsonMultiLineString], + * [com.google.maps.android.data.geojson.GeoJsonMultiPolygon] and + * [com.google.maps.android.data.geojson.GeoJsonGeometryCollection] + */ +open class MultiGeometry>( + /** + * Gets a list of Geometry objects + */ + override val geometryObject: List +) : Geometry> { + + /** + * Gets the type of geometry + * + * @return type of geometry + */ + override open val geometryType: String + get() = "MultiGeometry" + + /** + * Sets the geometries for this MultiGeometry + * + * @param geometries a list of geometries to set + */ + fun setGeometries(geometries: List) { + // This class is immutable, but the method is kept for compatibility. + // In a future version, this class could be made mutable if needed. + } + + override fun toString(): String { + val geometries = "geometries=$geometryObject" + return "MultiGeometry{$geometries}" + } +} \ No newline at end of file diff --git a/library/src/main/java/com/google/maps/android/data/Point.java b/library/src/main/java/com/google/maps/android/data/Point.java deleted file mode 100644 index 6988696cd..000000000 --- a/library/src/main/java/com/google/maps/android/data/Point.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.android.gms.maps.model.LatLng; - -import androidx.annotation.NonNull; - -/** - * An abstraction that shares the common properties of - * {@link com.google.maps.android.data.kml.KmlPoint KmlPoint} and - * {@link com.google.maps.android.data.geojson.GeoJsonPoint GeoJsonPoint} - */ -public class Point implements Geometry { - - private final static String GEOMETRY_TYPE = "Point"; - - private final LatLng mCoordinates; - - /** - * Creates a new Point object - * - * @param coordinates coordinates of Point to store - */ - public Point(LatLng coordinates) { - if (coordinates == null) { - throw new IllegalArgumentException("Coordinates cannot be null"); - } - mCoordinates = coordinates; - } - - /** - * Gets the type of geometry - * - * @return type of geometry - */ - public String getGeometryType() { - return GEOMETRY_TYPE; - } - - /** - * Gets the coordinates of the Point - * - * @return coordinates of the Point - */ - public LatLng getGeometryObject() { - return mCoordinates; - } - - @NonNull - @Override - public String toString() { - StringBuilder sb = new StringBuilder(GEOMETRY_TYPE).append("{"); - sb.append("\n coordinates=").append(mCoordinates); - sb.append("\n}\n"); - return sb.toString(); - } - -} diff --git a/library/src/main/java/com/google/maps/android/data/Point.kt b/library/src/main/java/com/google/maps/android/data/Point.kt new file mode 100644 index 000000000..d19f4087a --- /dev/null +++ b/library/src/main/java/com/google/maps/android/data/Point.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2025 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.data + +import com.google.android.gms.maps.model.LatLng + +/** + * An abstraction that shares the common properties of + * [com.google.maps.android.data.kml.KmlPoint] and + * [com.google.maps.android.data.geojson.GeoJsonPoint] + */ +open class Point(coordinates: LatLng) : Geometry { + + private val _coordinates: LatLng = coordinates + + /** + * Gets the coordinates of the Point + */ + open val coordinates: LatLng + get() = _coordinates + + /** + * Gets the type of geometry + */ + override val geometryType: String = "Point" + + /** + * Gets the geometry object + */ + override val geometryObject: LatLng = _coordinates + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Point + + return _coordinates == other._coordinates + } + + override fun hashCode(): Int { + return _coordinates.hashCode() + } + + override fun toString(): String { + return "Point(coordinates=$_coordinates)" + } +} \ No newline at end of file diff --git a/library/src/main/java/com/google/maps/android/data/Renderer.java b/library/src/main/java/com/google/maps/android/data/Renderer.java index 1f59fc257..63f36624a 100644 --- a/library/src/main/java/com/google/maps/android/data/Renderer.java +++ b/library/src/main/java/com/google/maps/android/data/Renderer.java @@ -77,7 +77,7 @@ * {@link com.google.maps.android.data.kml.KmlRenderer KmlRenderer} and * {@link com.google.maps.android.data.geojson.GeoJsonRenderer GeoJsonRenderer} */ -public class Renderer { +public class Renderer { private static final int MARKER_ICON_SIZE = 32; @@ -87,7 +87,7 @@ public class Renderer { private GoogleMap mMap; - private final BiMultiMap mFeatures = new BiMultiMap<>(); + private final BiMultiMap mFeatures = new BiMultiMap<>(); private HashMap mStyles; @@ -156,7 +156,7 @@ public Renderer(GoogleMap map, * @param polylineManager polyline manager to create polyline collection from * @param groundOverlayManager ground overlay manager to create ground overlay collection from */ - public Renderer(GoogleMap map, HashMap features, MarkerManager markerManager, PolygonManager polygonManager, PolylineManager polylineManager, GroundOverlayManager groundOverlayManager) { + public Renderer(GoogleMap map, HashMap features, MarkerManager markerManager, PolygonManager polygonManager, PolylineManager polylineManager, GroundOverlayManager groundOverlayManager) { this(map, null, new GeoJsonPointStyle(), new GeoJsonLineStringStyle(), new GeoJsonPolygonStyle(), null, markerManager, polygonManager, polylineManager, groundOverlayManager); mFeatures.putAll(features); mImagesCache = null; @@ -274,7 +274,7 @@ protected void putContainerFeature(Object mapObject, Feature placemark) { * * @return set containing Features */ - public Set getFeatures() { + public Set getFeatures() { return mFeatures.keySet(); } @@ -284,7 +284,7 @@ public Set getFeatures() { * @param mapObject Marker, Polyline or Polygon * @return Feature for the given map object */ - Feature getFeature(Object mapObject) { + T getFeature(Object mapObject) { return mFeatures.getKey(mapObject); } @@ -310,7 +310,7 @@ public Collection getValues() { * * @return mFeatures hashmap */ - protected HashMap getAllFeatures() { + protected HashMap getAllFeatures() { return mFeatures; } @@ -482,7 +482,7 @@ GeoJsonPolygonStyle getDefaultPolygonStyle() { * @param feature Feature to be added onto the map * @param object Corresponding map object to this feature */ - protected void putFeatures(Feature feature, Object object) { + protected void putFeatures(T feature, Object object) { mFeatures.put(feature, object); } @@ -610,7 +610,7 @@ protected void removeGroundOverlays(HashMap gro * * @param feature feature to remove from map */ - protected void removeFeature(Feature feature) { + protected void removeFeature(T feature) { // Check if given feature is stored if (mFeatures.containsKey(feature)) { removeFromMap(mFeatures.remove(feature)); @@ -652,7 +652,7 @@ protected void clearStylesRenderer() { */ protected void storeData(HashMap styles, HashMap styleMaps, - HashMap features, + HashMap features, ArrayList folders, HashMap groundOverlays) { mStyles = styles; @@ -667,7 +667,7 @@ protected void storeData(HashMap styles, * * @param feature feature to add to the map */ - protected void addFeature(Feature feature) { + protected void addFeature(T feature) { Object mapObject = FEATURE_NOT_ON_MAP; if (feature instanceof GeoJsonFeature) { setFeatureDefaultStyles((GeoJsonFeature) feature); diff --git a/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonFeature.java b/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonFeature.java index 72485d797..995c54378 100644 --- a/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonFeature.java +++ b/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonFeature.java @@ -54,7 +54,6 @@ public class GeoJsonFeature extends Feature implements Observer { public GeoJsonFeature(Geometry geometry, String id, HashMap properties, LatLngBounds boundingBox) { super(geometry, id, properties); - mId = id; mBoundingBox = boundingBox; } @@ -238,7 +237,7 @@ public String toString() { sb.append(",\n point style=").append(mPointStyle); sb.append(",\n line string style=").append(mLineStringStyle); sb.append(",\n polygon style=").append(mPolygonStyle); - sb.append(",\n id=").append(mId); + sb.append(",\n id=").append(getId()); sb.append(",\n properties=").append(getProperties()); sb.append("\n}\n"); return sb.toString(); diff --git a/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonGeometryCollection.java b/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonGeometryCollection.java index 5a63be9ec..a0a7a5203 100644 --- a/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonGeometryCollection.java +++ b/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonGeometryCollection.java @@ -31,17 +31,11 @@ public class GeoJsonGeometryCollection extends MultiGeometry { */ public GeoJsonGeometryCollection(List geometries) { super(geometries); - setGeometryType("GeometryCollection"); } - /** - * Gets the type of geometry. The type of geometry conforms to the GeoJSON 'type' - * specification. - * - * @return type of geometry - */ - public String getType() { - return getGeometryType(); + @Override + public String getGeometryType() { + return "GeometryCollection"; } /** diff --git a/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonLayer.java b/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonLayer.java index 1ee4b88fd..06f285c61 100644 --- a/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonLayer.java +++ b/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonLayer.java @@ -50,7 +50,7 @@ * To remove the rendered data from the layer * {@code layer.removeLayerFromMap();} */ -public class GeoJsonLayer extends Layer { +public class GeoJsonLayer extends Layer { private LatLngBounds mBoundingBox; @@ -172,8 +172,9 @@ public void addLayerToMap() { * * @return iterable of Feature elements */ + @Override public Iterable getFeatures() { - return (Iterable) super.getFeatures(); + return super.getFeatures(); } /** diff --git a/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonMultiLineString.java b/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonMultiLineString.java index 098ab3e67..0c96c6d22 100644 --- a/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonMultiLineString.java +++ b/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonMultiLineString.java @@ -32,17 +32,11 @@ public class GeoJsonMultiLineString extends MultiGeometry { */ public GeoJsonMultiLineString(List geoJsonLineStrings) { super(geoJsonLineStrings); - setGeometryType("MultiLineString"); } - /** - * Gets the type of geometry. The type of geometry conforms to the GeoJSON 'type' - * specification. - * - * @return type of geometry - */ - public String getType() { - return getGeometryType(); + @Override + public String getGeometryType() { + return "MultiLineString"; } /** diff --git a/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonMultiPoint.java b/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonMultiPoint.java index 0fd2d9f6d..d01a85bd9 100644 --- a/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonMultiPoint.java +++ b/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonMultiPoint.java @@ -32,17 +32,11 @@ public class GeoJsonMultiPoint extends MultiGeometry { */ public GeoJsonMultiPoint(List geoJsonPoints) { super(geoJsonPoints); - setGeometryType("MultiPoint"); } - /** - * Gets the type of geometry. The type of geometry conforms to the GeoJSON 'type' - * specification. - * - * @return type of geometry - */ - public String getType() { - return getGeometryType(); + @Override + public String getGeometryType() { + return "MultiPoint"; } /** diff --git a/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonMultiPolygon.java b/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonMultiPolygon.java index 76f40cf83..37466e0c3 100644 --- a/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonMultiPolygon.java +++ b/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonMultiPolygon.java @@ -33,17 +33,11 @@ public class GeoJsonMultiPolygon extends MultiGeometry { */ public GeoJsonMultiPolygon(List geoJsonPolygons) { super(geoJsonPolygons); - setGeometryType("MultiPolygon"); } - /** - * Gets the type of geometry. The type of geometry conforms to the GeoJSON 'type' - * specification. - * - * @return type of geometry - */ - public String getType() { - return getGeometryType(); + @Override + public String getGeometryType() { + return "MultiPolygon"; } /** diff --git a/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonRenderer.java b/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonRenderer.java index 86150d626..aff162332 100644 --- a/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonRenderer.java +++ b/library/src/main/java/com/google/maps/android/data/geojson/GeoJsonRenderer.java @@ -33,7 +33,7 @@ * Renders GeoJsonFeature objects onto the GoogleMap as Marker, Polyline and Polygon objects. Also * removes GeoJsonFeature objects and redraws features when updated. */ -public class GeoJsonRenderer extends Renderer implements Observer { +public class GeoJsonRenderer extends Renderer implements Observer { private final static Object FEATURE_NOT_ON_MAP = null; @@ -71,8 +71,8 @@ public void setMap(GoogleMap map) { public void addLayerToMap() { if (!isLayerOnMap()) { setLayerVisibility(true); - for (Feature feature : super.getFeatures()) { - addFeature((GeoJsonFeature) feature); + for (GeoJsonFeature feature : super.getFeatures()) { + addFeature(feature); } } } @@ -94,7 +94,7 @@ public void addFeature(@NonNull GeoJsonFeature feature) { */ public void removeLayerFromMap() { if (isLayerOnMap()) { - for (Feature feature : super.getFeatures()) { + for (GeoJsonFeature feature : super.getFeatures()) { removeFromMap(super.getAllFeatures().get(feature)); feature.deleteObserver(this); } @@ -157,4 +157,4 @@ public void update(Observable observable, Object data) { } } } -} +} \ No newline at end of file diff --git a/library/src/main/java/com/google/maps/android/data/kml/KmlLayer.java b/library/src/main/java/com/google/maps/android/data/kml/KmlLayer.java index 99e2f5007..f0656ffce 100644 --- a/library/src/main/java/com/google/maps/android/data/kml/KmlLayer.java +++ b/library/src/main/java/com/google/maps/android/data/kml/KmlLayer.java @@ -44,7 +44,7 @@ /** * Document class allows for users to input their KML data and output it onto the map */ -public class KmlLayer extends Layer { +public class KmlLayer extends Layer { /** * Creates a new KmlLayer object - addLayerToMap() must be called to trigger rendering onto a map. @@ -227,7 +227,7 @@ public boolean hasPlacemarks() { * @return iterable of KmlPlacemark objects */ public Iterable getPlacemarks() { - return (Iterable) getFeatures(); + return getFeatures(); } /** diff --git a/library/src/main/java/com/google/maps/android/data/kml/KmlMultiGeometry.java b/library/src/main/java/com/google/maps/android/data/kml/KmlMultiGeometry.java index 568388b46..f11795b7f 100644 --- a/library/src/main/java/com/google/maps/android/data/kml/KmlMultiGeometry.java +++ b/library/src/main/java/com/google/maps/android/data/kml/KmlMultiGeometry.java @@ -36,6 +36,11 @@ public KmlMultiGeometry(ArrayList geometries) { super(geometries); } + @Override + public String getGeometryType() { + return "MultiGeometry"; + } + /** * Gets an ArrayList of Geometry objects * diff --git a/library/src/main/java/com/google/maps/android/data/kml/KmlPolygon.java b/library/src/main/java/com/google/maps/android/data/kml/KmlPolygon.java index b58fe17db..ff2332cb8 100644 --- a/library/src/main/java/com/google/maps/android/data/kml/KmlPolygon.java +++ b/library/src/main/java/com/google/maps/android/data/kml/KmlPolygon.java @@ -27,7 +27,7 @@ * Represents a KML Polygon. Contains a single array of outer boundary coordinates and an array of * arrays for the inner boundary coordinates. */ -public class KmlPolygon implements DataPolygon>> { +public class KmlPolygon implements DataPolygon>> { public static final String GEOMETRY_TYPE = "Polygon"; diff --git a/library/src/main/java/com/google/maps/android/data/kml/KmlRenderer.java b/library/src/main/java/com/google/maps/android/data/kml/KmlRenderer.java index 93c38b0a3..c1a804de4 100644 --- a/library/src/main/java/com/google/maps/android/data/kml/KmlRenderer.java +++ b/library/src/main/java/com/google/maps/android/data/kml/KmlRenderer.java @@ -55,7 +55,7 @@ * Renders all visible KmlPlacemark and KmlGroundOverlay objects onto the GoogleMap as Marker, * Polyline, Polygon, GroundOverlay objects. Also removes objects from the map. */ -public class KmlRenderer extends Renderer { +public class KmlRenderer extends Renderer { private static final String LOG_TAG = "KmlRenderer"; @@ -85,7 +85,7 @@ public class KmlRenderer extends Renderer { * * @param placemarks placemarks to remove */ - private void removePlacemarks(HashMap placemarks) { + private void removePlacemarks(HashMap placemarks) { // Remove map object from the map removeFeatures(placemarks); } @@ -254,8 +254,8 @@ public void removeLayerFromMap() { * * @param placemarks */ - private void addPlacemarksToMap(HashMap placemarks) { - for (Feature kmlPlacemark : placemarks.keySet()) { + private void addPlacemarksToMap(HashMap placemarks) { + for (KmlPlacemark kmlPlacemark : placemarks.keySet()) { addFeature(kmlPlacemark); } } @@ -327,7 +327,7 @@ private void downloadMarkerIcons() { * @param placemarks map of placemark to features */ private void addIconToMarkers(String iconUrl, HashMap placemarks) { - for (Feature placemark : placemarks.keySet()) { + for (KmlPlacemark placemark : placemarks.keySet()) { KmlStyle urlStyle = getStylesRenderer().get(placemark.getId()); KmlStyle inlineStyle = ((KmlPlacemark) placemark).getInlineStyle(); Geometry geometry = placemark.getGeometry(); @@ -558,7 +558,7 @@ protected void onPostExecute(Bitmap bitmap) { } else { cacheBitmap(mIconUrl, bitmap); if (isLayerOnMap()) { - addIconToMarkers(mIconUrl, (HashMap) getAllFeatures()); + addIconToMarkers(mIconUrl, getAllFeatures()); addContainerGroupIconsToMarkers(mIconUrl, mContainers); } } diff --git a/library/src/test/java/com/google/maps/android/SphericalUtilKotlinTest.kt b/library/src/test/java/com/google/maps/android/SphericalUtilKotlinTest.kt new file mode 100644 index 000000000..3f9afc108 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/SphericalUtilKotlinTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2025 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.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import kotlin.math.abs + +/** + * Tests for [SphericalUtil] that are written in Kotlin. + */ +class SphericalUtilKotlinTest { + + val testPolygon = """ + -104.9596325,39.7543772,0 + -104.9596969,39.7448581,0 + -104.959375,39.7446271,0 + -104.959096,39.7443136,0 + -104.9588171,39.7440166,0 + -104.9581305,39.7439176,0 + -104.9409429,39.7438681,0 + -104.9408785,39.7543277,0 + -104.9596325,39.7543772,0 + """.trimIndent().lines().map { + val (lng, lat, _) = it.split(",") + LatLng(lat.toDouble(), lng.toDouble()) + } + + // The expected length (perimeter) of the test polygon in meters. + private val EXPECTED_LENGTH_METERS = 5474.0 + + // The expected area of the test polygon in square meters. + private val EXPECTED_AREA_SQ_METERS = 1859748.0 + + // A tolerance for comparing floating-point numbers. + private val TOLERANCE = 1.0 // 1 meter or 1 sq meter + + /** + * Tests the `computeLength` method with the polygon from the KML file. + */ + @Test + fun testComputeLengthWithKmlPolygon() { + val calculatedLength = SphericalUtil.computeLength(testPolygon) + assertThat(calculatedLength).isWithin(TOLERANCE).of(EXPECTED_LENGTH_METERS) + } + + /** + * Tests the `computeSignedArea` method with the polygon from the KML file. + * Note: We test the absolute value since computeArea simply wraps computeSignedArea with abs(). + */ + @Test + fun testComputeSignedAreaWithKmlPolygon() { + val calculatedSignedArea = SphericalUtil.computeSignedArea(testPolygon) + assertThat(abs(calculatedSignedArea)).isWithin(TOLERANCE).of(EXPECTED_AREA_SQ_METERS) + } +} \ No newline at end of file diff --git a/library/src/test/java/com/google/maps/android/data/FeatureTest.java b/library/src/test/java/com/google/maps/android/data/FeatureTest.java deleted file mode 100644 index 833ee0025..000000000 --- a/library/src/test/java/com/google/maps/android/data/FeatureTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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; - -import com.google.android.gms.maps.model.LatLng; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.*; - -public class FeatureTest { - @Test - public void testGetId() { - Feature feature = new Feature(null, "Pirate", null); - assertNotNull(feature.getId()); - assertEquals("Pirate", feature.getId()); - feature = new Feature(null, null, null); - assertNull(feature.getId()); - } - - @Test - public void testProperty() { - Map properties = new HashMap<>(); - properties.put("Color", "Red"); - properties.put("Width", "3"); - Feature feature = new Feature(null, null, properties); - assertFalse(feature.hasProperty("llama")); - assertTrue(feature.hasProperty("Color")); - assertEquals("Red", feature.getProperty("Color")); - assertTrue(feature.hasProperty("Width")); - assertEquals("3", feature.getProperty("Width")); - assertNull(feature.removeProperty("banana")); - assertEquals("3", feature.removeProperty("Width")); - assertNull(feature.setProperty("Width", "10")); - assertEquals("10", feature.setProperty("Width", "500")); - } - - @Test - public void testGeometry() { - Feature feature = new Feature(null, null, null); - assertNull(feature.getGeometry()); - Point point = new Point(new LatLng(0, 0)); - feature.setGeometry(point); - assertEquals(point, feature.getGeometry()); - feature.setGeometry(null); - assertNull(feature.getGeometry()); - - LineString lineString = - new LineString( - new ArrayList<>(Arrays.asList(new LatLng(0, 0), new LatLng(50, 50)))); - feature = new Feature(lineString, null, null); - assertEquals(lineString, feature.getGeometry()); - feature.setGeometry(point); - assertEquals(point, feature.getGeometry()); - feature.setGeometry(null); - assertNull(feature.getGeometry()); - feature.setGeometry(lineString); - assertEquals(lineString, feature.getGeometry()); - } -} diff --git a/library/src/test/java/com/google/maps/android/data/FeatureTest.kt b/library/src/test/java/com/google/maps/android/data/FeatureTest.kt new file mode 100644 index 000000000..f943d1885 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/data/FeatureTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright 2025 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.data + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import java.util.Observable +import java.util.Observer + +class FeatureTest { + + // Test subclass to access protected members + private class TestFeature( + geometry: Geometry<*>?, + id: String?, + properties: Map? + ) : Feature(geometry, id, properties) { + public override var id: String? + get() = super.id + set(value) { + super.id = value + } + + public override var geometry: Geometry<*>? + get() = super.geometry + set(value) { + super.geometry = value + } + + public override fun setProperty(property: String, propertyValue: String): String? { + return super.setProperty(property, propertyValue) + } + + public override fun removeProperty(property: String): String? { + return super.removeProperty(property) + } + } + + @Test + fun `getId returns correct id`() { + var feature: Feature = Feature(null, "Pirate", null) + assertThat(feature.id).isEqualTo("Pirate") + feature = Feature(null, null, null) + assertThat(feature.id).isNull() + } + + @Test + fun `properties work as expected`() { + val properties = mapOf("Color" to "Red", "Width" to "3") + val feature = Feature(null, null, properties) + + assertThat(feature.hasProperty("llama")).isFalse() + assertThat(feature.hasProperty("Color")).isTrue() + assertThat(feature.getProperty("Color")).isEqualTo("Red") + assertThat(feature.hasProperties()).isTrue() + assertThat(feature.propertyKeys).containsExactly("Color", "Width") + } + + @Test + fun `protected property methods work as expected`() { + val testFeature = TestFeature(null, null, mutableMapOf("Color" to "Red", "Width" to "3")) + + assertThat(testFeature.removeProperty("Width")).isEqualTo("3") + assertThat(testFeature.hasProperty("Width")).isFalse() + + assertThat(testFeature.setProperty("Width", "10")).isNull() + assertThat(testFeature.getProperty("Width")).isEqualTo("10") + + assertThat(testFeature.setProperty("Width", "500")).isEqualTo("10") + assertThat(testFeature.getProperty("Width")).isEqualTo("500") + } + + @Test + fun `geometry works as expected`() { + val feature = Feature(null, null, null) + assertThat(feature.hasGeometry()).isFalse() + assertThat(feature.geometry).isNull() + + val point = Point(LatLng(0.0, 0.0)) + val featureWithPoint = Feature(point, null, null) + assertThat(featureWithPoint.hasGeometry()).isTrue() + assertThat(featureWithPoint.geometry).isEqualTo(point) + } + + @Test + fun `protected geometry setter works`() { + val testFeature = TestFeature(null, null, null) + val point = Point(LatLng(0.0, 0.0)) + testFeature.geometry = point + assertThat(testFeature.geometry).isEqualTo(point) + } + + @Test + fun `observable notifies on change`() { + val feature = TestFeature(null, null, null) + val observer = TestObserver() + feature.addObserver(observer) + + feature.setProperty("key", "value") + assertThat(observer.wasUpdated).isTrue() + observer.wasUpdated = false // reset + + feature.removeProperty("key") + assertThat(observer.wasUpdated).isTrue() + observer.wasUpdated = false // reset + + feature.geometry = Point(LatLng(1.0, 1.0)) + assertThat(observer.wasUpdated).isTrue() + } + + class TestObserver : Observer { + var wasUpdated = false + override fun update(o: Observable?, arg: Any?) { + wasUpdated = true + } + } +} \ No newline at end of file diff --git a/library/src/test/java/com/google/maps/android/data/LayerTest.kt b/library/src/test/java/com/google/maps/android/data/LayerTest.kt new file mode 100644 index 000000000..ffdb216f9 --- /dev/null +++ b/library/src/test/java/com/google/maps/android/data/LayerTest.kt @@ -0,0 +1,191 @@ +/* + * Copyright 2025 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.data + +import com.google.android.gms.maps.GoogleMap +import com.google.common.truth.Truth.assertThat +import com.google.maps.android.data.kml.KmlContainer +import com.google.maps.android.data.kml.KmlGroundOverlay +import com.google.maps.android.data.kml.KmlPlacemark +import com.google.maps.android.data.kml.KmlRenderer +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.Test + +class LayerTest { + + private class TestLayer : Layer() { + override fun addLayerToMap() {} + + // Expose protected members for testing + fun setRenderer(renderer: Renderer) { + this.renderer = renderer + } + + public override fun hasFeatures(): Boolean = super.hasFeatures() + + public override fun hasContainers(): Boolean = super.hasContainers() + + public override fun getContainers(): Iterable? = super.getContainers() + + public override fun getGroundOverlays(): Iterable? = super.getGroundOverlays() + + public override fun addFeature(feature: T) { + super.addFeature(feature) + } + + public override fun removeFeature(feature: T) { + super.removeFeature(feature) + } + } + + @Test + fun `removeLayerFromMap calls renderer`() { + val renderer = mockk(relaxed = true) + val layer = TestLayer() + layer.setRenderer(renderer) + layer.removeLayerFromMap() + verify { renderer.removeLayerFromMap() } + } + + @Test + fun `setOnFeatureClickListener calls renderer`() { + val renderer = mockk>(relaxed = true) + val layer = TestLayer() + layer.setRenderer(renderer) + val listener = Layer.OnFeatureClickListener { } + layer.setOnFeatureClickListener(listener) + verify { renderer.setOnFeatureClickListener(listener) } + } + + @Test + fun `getFeatures calls renderer`() { + val renderer = mockk>() + val layer = TestLayer() + layer.setRenderer(renderer) + val features = emptySet() + every { renderer.getFeatures() } returns features + assertThat(layer.getFeatures()).isSameInstanceAs(features) + } + + @Test + fun `getFeature calls renderer`() { + val renderer = mockk>(relaxed = true) + val layer = TestLayer() + layer.setRenderer(renderer) + val feature = mockk() + every { renderer.getFeature(any()) } returns feature + assertThat(layer.getFeature(Any())).isSameInstanceAs(feature) + } + + @Test + fun `getContainerFeature calls renderer`() { + val renderer = mockk>(relaxed = true) + val layer = TestLayer() + layer.setRenderer(renderer) + val feature = mockk() + every { renderer.getContainerFeature(any()) } returns feature + assertThat(layer.getContainerFeature(Any())).isSameInstanceAs(feature) + } + + @Test + fun `hasFeatures calls renderer`() { + val renderer = mockk>() + val layer = TestLayer() + layer.setRenderer(renderer) + every { renderer.hasFeatures() } returns true + assertThat(layer.hasFeatures()).isTrue() + } + + @Test + fun `hasContainers calls renderer`() { + val renderer = mockk() + val layer = TestLayer() + layer.setRenderer(renderer) + every { renderer.hasNestedContainers() } returns true + assertThat(layer.hasContainers()).isTrue() + } + + @Test + fun `getContainers calls renderer`() { + val renderer = mockk() + val layer = TestLayer() + layer.setRenderer(renderer) + val containers = emptyList() + every { renderer.getNestedContainers() } returns containers + assertThat(layer.getContainers()).isSameInstanceAs(containers) + } + + @Test + fun `getGroundOverlays calls renderer`() { + val renderer = mockk() + val layer = TestLayer() + layer.setRenderer(renderer) + val overlays = emptySet() + every { renderer.getGroundOverlays() } returns overlays + assertThat(layer.getGroundOverlays()).isSameInstanceAs(overlays) + } + + @Test + fun `map property calls renderer`() { + val renderer = mockk>() + val layer = TestLayer() + layer.setRenderer(renderer) + val map = mockk() + every { renderer.map } returns map + assertThat(layer.map).isSameInstanceAs(map) + } + + @Test + fun `setMap calls renderer`() { + val renderer = mockk>(relaxed = true) + val layer = TestLayer() + layer.setRenderer(renderer) + val map = mockk() + layer.setMap(map) + verify { renderer.map = map } + } + + @Test + fun `isLayerOnMap property calls renderer`() { + val renderer = mockk>() + val layer = TestLayer() + layer.setRenderer(renderer) + every { renderer.isLayerOnMap } returns true + assertThat(layer.isLayerOnMap).isTrue() + } + + @Test + fun `addFeature calls renderer`() { + val renderer = mockk>(relaxed = true) + val layer = TestLayer() + layer.setRenderer(renderer) + val feature = mockk() + layer.addFeature(feature) + verify { renderer.addFeature(feature) } + } + + @Test + fun `removeFeature calls renderer`() { + val renderer = mockk>(relaxed = true) + val layer = TestLayer() + layer.setRenderer(renderer) + val feature = mockk() + layer.removeFeature(feature) + verify { renderer.removeFeature(feature) } + } +} \ No newline at end of file diff --git a/library/src/test/java/com/google/maps/android/data/LineStringTest.java b/library/src/test/java/com/google/maps/android/data/LineStringTest.java deleted file mode 100644 index e17bcdb80..000000000 --- a/library/src/test/java/com/google/maps/android/data/LineStringTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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; - -import com.google.android.gms.maps.model.LatLng; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -public class LineStringTest { - private LineString createSimpleLineString() { - List coordinates = new ArrayList<>(); - coordinates.add(new LatLng(95, 60)); - coordinates.add(new LatLng(93, 57)); - coordinates.add(new LatLng(95, 55)); - coordinates.add(new LatLng(95, 53)); - coordinates.add(new LatLng(91, 54)); - coordinates.add(new LatLng(86, 56)); - return new LineString(coordinates); - } - - private LineString createLoopedLineString() { - List coordinates = new ArrayList<>(); - coordinates.add(new LatLng(92, 66)); - coordinates.add(new LatLng(89, 64)); - coordinates.add(new LatLng(94, 62)); - coordinates.add(new LatLng(92, 66)); - return new LineString(coordinates); - } - - @Test - public void testGetType() { - LineString lineString = createSimpleLineString(); - assertNotNull(lineString); - assertNotNull(lineString.getGeometryType()); - assertEquals("LineString", lineString.getGeometryType()); - lineString = createLoopedLineString(); - assertNotNull(lineString); - assertNotNull(lineString.getGeometryType()); - assertEquals("LineString", lineString.getGeometryType()); - } - - @Test - public void testGetGeometryObject() { - LineString lineString = createSimpleLineString(); - assertNotNull(lineString); - assertNotNull(lineString.getGeometryObject()); - assertEquals(lineString.getGeometryObject().size(), 6); - assertEquals(lineString.getGeometryObject().get(0).latitude, 90.0, 0); - assertEquals(lineString.getGeometryObject().get(1).latitude, 90.0, 0); - assertEquals(lineString.getGeometryObject().get(2).latitude, 90.0, 0); - assertEquals(lineString.getGeometryObject().get(3).longitude, 53.0, 0); - assertEquals(lineString.getGeometryObject().get(4).longitude, 54.0, 0); - lineString = createLoopedLineString(); - assertNotNull(lineString); - assertNotNull(lineString.getGeometryObject()); - assertEquals(lineString.getGeometryObject().size(), 4); - assertEquals(lineString.getGeometryObject().get(0).latitude, 90.0, 0); - assertEquals(lineString.getGeometryObject().get(1).latitude, 89.0, 0); - assertEquals(lineString.getGeometryObject().get(2).longitude, 62.0, 0); - assertEquals(lineString.getGeometryObject().get(3).longitude, 66.0, 0); - } -} diff --git a/library/src/test/java/com/google/maps/android/data/LineStringTest.kt b/library/src/test/java/com/google/maps/android/data/LineStringTest.kt new file mode 100644 index 000000000..6bb6f61cd --- /dev/null +++ b/library/src/test/java/com/google/maps/android/data/LineStringTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2025 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.data + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows +import org.junit.Test +import java.lang.reflect.InvocationTargetException + +class LineStringTest { + private fun createSimpleLineString(): LineString { + val coordinates = listOf( + LatLng(95.0, 60.0), // Note: latitude is clamped to 90.0 + LatLng(93.0, 57.0), // Note: latitude is clamped to 90.0 + LatLng(95.0, 55.0), // Note: latitude is clamped to 90.0 + LatLng(95.0, 53.0), // Note: latitude is clamped to 90.0 + LatLng(91.0, 54.0), // Note: latitude is clamped to 90.0 + LatLng(86.0, 56.0) + ) + return LineString(coordinates) + } + + @Test + fun `geometryType returns correct type`() { + val lineString = createSimpleLineString() + assertThat(lineString.geometryType).isEqualTo("LineString") + } + + @Test + fun `geometryObject returns correct coordinates`() { + val lineString = createSimpleLineString() + val expectedCoordinates = listOf( + LatLng(90.0, 60.0), + LatLng(90.0, 57.0), + LatLng(90.0, 55.0), + LatLng(90.0, 53.0), + LatLng(90.0, 54.0), + LatLng(86.0, 56.0) + ) + assertThat(lineString.geometryObject).containsExactlyElementsIn(expectedCoordinates).inOrder() + } + + @Test + fun `constructor throws for null coordinates`() { + val exception = assertThrows(InvocationTargetException::class.java) { + val constructor = LineString::class.java.getConstructor(List::class.java) + constructor.newInstance(null) + } + assertThat(exception.cause).isInstanceOf(NullPointerException::class.java) + } + + @Test + fun `equals and hashCode work as expected`() { + val lineString1 = createSimpleLineString() + val lineString2 = createSimpleLineString() + val lineString3 = LineString(listOf(LatLng(1.0, 2.0))) + + assertThat(lineString1).isEqualTo(lineString2) + assertThat(lineString1.hashCode()).isEqualTo(lineString2.hashCode()) + assertThat(lineString1).isNotEqualTo(lineString3) + assertThat(lineString1.hashCode()).isNotEqualTo(lineString3.hashCode()) + } + + @Test + fun `toString returns correct string representation`() { + val lineString = LineString(listOf(LatLng(1.0, 2.0))) + assertThat(lineString.toString()).isEqualTo("LineString(coordinates=[lat/lng: (1.0,2.0)])") + } +} diff --git a/library/src/test/java/com/google/maps/android/data/MultiGeometryTest.java b/library/src/test/java/com/google/maps/android/data/MultiGeometryTest.java deleted file mode 100644 index 3d1b66a20..000000000 --- a/library/src/test/java/com/google/maps/android/data/MultiGeometryTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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; - -import com.google.android.gms.maps.model.LatLng; -import com.google.maps.android.data.geojson.GeoJsonPolygon; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -public class MultiGeometryTest { - private MultiGeometry mg; - - @Test - public void testGetGeometryType() { - List lineStrings = new ArrayList<>(); - lineStrings.add( - new LineString( - new ArrayList<>(Arrays.asList(new LatLng(0, 0), new LatLng(50, 50))))); - lineStrings.add( - new LineString( - new ArrayList<>(Arrays.asList(new LatLng(56, 65), new LatLng(23, 23))))); - mg = new MultiGeometry(lineStrings); - assertEquals("MultiGeometry", mg.getGeometryType()); - List polygons = new ArrayList<>(); - List> polygon = new ArrayList<>(); - polygon.add( - new ArrayList<>( - Arrays.asList( - new LatLng(0, 0), - new LatLng(20, 20), - new LatLng(60, 60), - new LatLng(0, 0)))); - polygons.add(new GeoJsonPolygon(polygon)); - polygon = new ArrayList<>(); - polygon.add( - new ArrayList<>( - Arrays.asList( - new LatLng(0, 0), - new LatLng(50, 80), - new LatLng(10, 15), - new LatLng(0, 0)))); - polygon.add( - new ArrayList<>( - Arrays.asList( - new LatLng(0, 0), - new LatLng(20, 20), - new LatLng(60, 60), - new LatLng(0, 0)))); - polygons.add(new GeoJsonPolygon(polygon)); - mg = new MultiGeometry(polygons); - assertEquals("MultiGeometry", mg.getGeometryType()); - } - - @Test - public void testSetGeometryType() { - List lineStrings = new ArrayList<>(); - lineStrings.add( - new LineString( - new ArrayList<>(Arrays.asList(new LatLng(0, 0), new LatLng(50, 50))))); - lineStrings.add( - new LineString( - new ArrayList<>(Arrays.asList(new LatLng(56, 65), new LatLng(23, 23))))); - mg = new MultiGeometry(lineStrings); - assertEquals("MultiGeometry", mg.getGeometryType()); - mg.setGeometryType("MultiLineString"); - assertEquals("MultiLineString", mg.getGeometryType()); - } - - @Test - public void testGetGeometryObject() { - List points = new ArrayList<>(); - points.add(new Point(new LatLng(0, 0))); - points.add(new Point(new LatLng(5, 5))); - points.add(new Point(new LatLng(10, 10))); - mg = new MultiGeometry(points); - assertEquals(points, mg.getGeometryObject()); - points = new ArrayList<>(); - mg = new MultiGeometry(points); - assertEquals(new ArrayList(), mg.getGeometryObject()); - - try { - mg = new MultiGeometry(null); - fail(); - } catch (IllegalArgumentException e) { - assertEquals("Geometries cannot be null", e.getMessage()); - } - } -} diff --git a/library/src/test/java/com/google/maps/android/data/MultiGeometryTest.kt b/library/src/test/java/com/google/maps/android/data/MultiGeometryTest.kt new file mode 100644 index 000000000..e633e7a0f --- /dev/null +++ b/library/src/test/java/com/google/maps/android/data/MultiGeometryTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2025 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.data + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import com.google.maps.android.data.geojson.GeoJsonPolygon +import org.junit.Assert.assertThrows +import org.junit.Test +import java.lang.reflect.InvocationTargetException + +class MultiGeometryTest { + + @Test + fun `geometryType is correct`() { + val multiGeometry = MultiGeometry(emptyList>()) + assertThat(multiGeometry.geometryType).isEqualTo("MultiGeometry") + } + + @Test + fun `geometryObject returns correct geometries`() { + val points = listOf( + Point(LatLng(0.0, 0.0)), + Point(LatLng(5.0, 5.0)), + Point(LatLng(10.0, 10.0)) + ) + val multiGeometry = MultiGeometry(points) + assertThat(multiGeometry.geometryObject).containsExactlyElementsIn(points).inOrder() + + val emptyPoints = emptyList() + val emptyMultiGeometry = MultiGeometry(emptyPoints) + assertThat(emptyMultiGeometry.geometryObject).isEmpty() + } + + @Test + fun `constructor throws for null geometries`() { + val exception = assertThrows(InvocationTargetException::class.java) { + // Using reflection to simulate a Java call with a null value + val constructor = MultiGeometry::class.java.getConstructor(List::class.java) + constructor.newInstance(null) + } + assertThat(exception.cause).isInstanceOf(NullPointerException::class.java) + } + + @Test + fun `setGeometries is a no-op`() { + val geometry1 = Point(LatLng(1.0, 1.0)) + val multiGeometry = MultiGeometry(listOf(geometry1)) + + val geometry2 = Point(LatLng(2.0, 2.0)) + multiGeometry.setGeometries(listOf(geometry2)) + + // Assert that the geometry object has not changed + assertThat(multiGeometry.geometryObject).containsExactly(geometry1) + } + + @Test + fun `test with LineString geometries`() { + val lineStrings = listOf( + LineString(listOf(LatLng(0.0, 0.0), LatLng(50.0, 50.0))), + LineString(listOf(LatLng(56.0, 65.0), LatLng(23.0, 23.0))) + ) + val multiGeometry = MultiGeometry(lineStrings) + assertThat(multiGeometry.geometryType).isEqualTo("MultiGeometry") + assertThat(multiGeometry.geometryObject).containsExactlyElementsIn(lineStrings).inOrder() + } + + @Test + fun `test with Polygon geometries`() { + val polygons = listOf( + GeoJsonPolygon(listOf(listOf(LatLng(0.0, 0.0), LatLng(20.0, 20.0), LatLng(60.0, 60.0), LatLng(0.0, 0.0)))), + GeoJsonPolygon(listOf( + listOf(LatLng(0.0, 0.0), LatLng(50.0, 80.0), LatLng(10.0, 15.0), LatLng(0.0, 0.0)), + listOf(LatLng(0.0, 0.0), LatLng(20.0, 20.0), LatLng(60.0, 60.0), LatLng(0.0, 0.0)) + )) + ) + val multiGeometry = MultiGeometry(polygons) + assertThat(multiGeometry.geometryType).isEqualTo("MultiGeometry") + assertThat(multiGeometry.geometryObject).containsExactlyElementsIn(polygons).inOrder() + } +} \ No newline at end of file diff --git a/library/src/test/java/com/google/maps/android/data/PointTest.java b/library/src/test/java/com/google/maps/android/data/PointTest.java deleted file mode 100644 index 61ca6bef9..000000000 --- a/library/src/test/java/com/google/maps/android/data/PointTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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; - -import com.google.android.gms.maps.model.LatLng; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -public class PointTest { - @Test - public void testGetGeometryType() { - Point p = new Point(new LatLng(0, 50)); - assertEquals("Point", p.getGeometryType()); - } - - @Test - public void testGetGeometryObject() { - Point p = new Point(new LatLng(0, 50)); - assertEquals(new LatLng(0, 50), p.getGeometryObject()); - try { - new Point(null); - fail(); - } catch (IllegalArgumentException e) { - assertEquals("Coordinates cannot be null", e.getMessage()); - } - } -} diff --git a/library/src/test/java/com/google/maps/android/data/PointTest.kt b/library/src/test/java/com/google/maps/android/data/PointTest.kt new file mode 100644 index 000000000..95519892d --- /dev/null +++ b/library/src/test/java/com/google/maps/android/data/PointTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2025 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.data + +import com.google.android.gms.maps.model.LatLng +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows +import org.junit.Test +import java.lang.reflect.InvocationTargetException + +class PointTest { + @Test + fun `geometryType returns correct type`() { + val point = Point(LatLng(0.0, 50.0)) + assertThat(point.geometryType).isEqualTo("Point") + } + + @Test + fun `geometryObject returns correct coordinates`() { + val coordinates = LatLng(0.0, 50.0) + val point = Point(coordinates) + assertThat(point.geometryObject).isEqualTo(coordinates) + assertThat(point.coordinates).isEqualTo(coordinates) + } + + @Test + fun `constructor throws for null coordinates`() { + // In Kotlin, Point(null) is a compile-time error. + // This test verifies the runtime exception for Java compatibility. + val exception = assertThrows(InvocationTargetException::class.java) { + // Using reflection to simulate a Java call with a null value + val constructor = Point::class.java.getConstructor(LatLng::class.java) + constructor.newInstance(null) + } + assertThat(exception.cause).isInstanceOf(NullPointerException::class.java) + } + + @Test + fun `equals and hashCode work as expected`() { + val point1 = Point(LatLng(10.0, 20.0)) + val point2 = Point(LatLng(10.0, 20.0)) + val point3 = Point(LatLng(30.0, 40.0)) + + assertThat(point1).isEqualTo(point2) + assertThat(point1.hashCode()).isEqualTo(point2.hashCode()) + assertThat(point1).isNotEqualTo(point3) + assertThat(point1.hashCode()).isNotEqualTo(point3.hashCode()) + assertThat(point1).isNotEqualTo(null) + } + + @Test + fun `toString returns correct string representation`() { + val point = Point(LatLng(1.0, 2.0)) + assertThat(point.toString()).isEqualTo("Point(coordinates=lat/lng: (1.0,2.0))") + } +} \ No newline at end of file diff --git a/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonLineStringTest.java b/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonLineStringTest.java index a3cb581c2..1ad8b735b 100644 --- a/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonLineStringTest.java +++ b/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonLineStringTest.java @@ -50,8 +50,9 @@ public void testGetCoordinates() { try { ls = new GeoJsonLineString(null); fail(); - } catch (IllegalArgumentException e) { - assertEquals("Coordinates cannot be null", e.getMessage()); + } catch (NullPointerException e) { + // Expected, as the underlying LineString class is now in Kotlin + // with a non-null constructor parameter. } } @@ -71,4 +72,4 @@ public void testGetAltitudes() { assertEquals(ls.getAltitudes().get(1), 200.0, 0); assertEquals(ls.getAltitudes().get(2), 300.0, 0); } -} +} \ No newline at end of file diff --git a/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonMultiLineStringTest.java b/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonMultiLineStringTest.java index ad2992c7f..831ed9c01 100644 --- a/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonMultiLineStringTest.java +++ b/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonMultiLineStringTest.java @@ -39,7 +39,7 @@ public void testGetType() { new GeoJsonLineString( new ArrayList<>(Arrays.asList(new LatLng(80, 10), new LatLng(-54, 12.7))))); mls = new GeoJsonMultiLineString(lineStrings); - assertEquals("MultiLineString", mls.getType()); + assertEquals("MultiLineString", mls.getGeometryType()); } @Test @@ -55,14 +55,15 @@ public void testGetLineStrings() { assertEquals(lineStrings, mls.getLineStrings()); lineStrings = new ArrayList<>(); - mls = new GeoJsonMultiLineString(lineStrings); + mls = new GeoJsonMultiLineString(new ArrayList()); assertEquals(new ArrayList(), mls.getLineStrings()); try { mls = new GeoJsonMultiLineString(null); fail(); - } catch (IllegalArgumentException e) { - assertEquals("Geometries cannot be null", e.getMessage()); + } catch (NullPointerException e) { + // Expected, as the underlying MultiGeometry class is now in Kotlin + // with a non-null constructor parameter. } } } diff --git a/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonMultiPointTest.java b/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonMultiPointTest.java index 51acadad2..afa36a434 100644 --- a/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonMultiPointTest.java +++ b/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonMultiPointTest.java @@ -35,7 +35,7 @@ public void testGetType() { points.add(new GeoJsonPoint(new LatLng(5, 5))); points.add(new GeoJsonPoint(new LatLng(10, 10))); mp = new GeoJsonMultiPoint(points); - assertEquals("MultiPoint", mp.getType()); + assertEquals("MultiPoint", mp.getGeometryType()); } @Test @@ -48,14 +48,15 @@ public void testGetPoints() { assertEquals(points, mp.getPoints()); points = new ArrayList<>(); - mp = new GeoJsonMultiPoint(points); + mp = new GeoJsonMultiPoint(new ArrayList()); assertEquals(new ArrayList(), mp.getPoints()); try { mp = new GeoJsonMultiPoint(null); fail(); - } catch (IllegalArgumentException e) { - assertEquals("Geometries cannot be null", e.getMessage()); + } catch (NullPointerException e) { + // Expected, as the underlying MultiGeometry class is now in Kotlin + // with a non-null constructor parameter. } } } diff --git a/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonMultiPolygonTest.java b/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonMultiPolygonTest.java index 21dfb0e8b..27922c2e0 100644 --- a/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonMultiPolygonTest.java +++ b/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonMultiPolygonTest.java @@ -58,7 +58,7 @@ public void testGetType() { new LatLng(0, 0)))); polygons.add(new GeoJsonPolygon(polygon)); mp = new GeoJsonMultiPolygon(polygons); - assertEquals("MultiPolygon", mp.getType()); + assertEquals("MultiPolygon", mp.getGeometryType()); } @Test @@ -93,14 +93,15 @@ public void testGetPolygons() { assertEquals(polygons, mp.getPolygons()); polygons = new ArrayList<>(); - mp = new GeoJsonMultiPolygon(polygons); + mp = new GeoJsonMultiPolygon(new ArrayList()); assertEquals(new ArrayList(), mp.getPolygons()); try { mp = new GeoJsonMultiPolygon(null); fail(); - } catch (IllegalArgumentException e) { - assertEquals("Geometries cannot be null", e.getMessage()); + } catch (NullPointerException e) { + // Expected, as the underlying MultiGeometry class is now in Kotlin + // with a non-null constructor parameter. } } } diff --git a/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonPointTest.java b/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonPointTest.java index 0cc52c4e7..699cd180a 100644 --- a/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonPointTest.java +++ b/library/src/test/java/com/google/maps/android/data/geojson/GeoJsonPointTest.java @@ -36,8 +36,9 @@ public void testGetCoordinates() { try { new GeoJsonPoint(null); fail(); - } catch (IllegalArgumentException e) { - assertEquals("Coordinates cannot be null", e.getMessage()); + } catch (NullPointerException e) { + // Expected, as the underlying Point class is now in Kotlin + // with a non-null constructor parameter. } } @@ -46,4 +47,4 @@ public void testGetAltitude() { GeoJsonPoint p = new GeoJsonPoint(new LatLng(0, 0), 100d); assertEquals(new Double(100), p.getAltitude()); } -} +} \ No newline at end of file diff --git a/library/src/test/java/com/google/maps/android/data/kml/KmlMultiGeometryTest.java b/library/src/test/java/com/google/maps/android/data/kml/KmlMultiGeometryTest.java index 0625e86a8..d71933387 100644 --- a/library/src/test/java/com/google/maps/android/data/kml/KmlMultiGeometryTest.java +++ b/library/src/test/java/com/google/maps/android/data/kml/KmlMultiGeometryTest.java @@ -57,8 +57,9 @@ public void testNullGeometry() { try { new KmlMultiGeometry(null); Assert.fail(); - } catch (IllegalArgumentException e) { - Assert.assertEquals("Geometries cannot be null", e.getMessage()); + } catch (NullPointerException e) { + // Expected, as the underlying MultiGeometry class is now in Kotlin + // with a non-null constructor parameter. } } }