3

The following minimal example runs fine only when assertions are disabled:

src/main/kotlin/main.kt:

package com.regions

import org.geotools.geojson.feature.FeatureJSON
import org.geotools.geometry.jts.JTS
import org.geotools.referencing.CRS
import org.locationtech.jts.geom.Polygon
import java.nio.charset.StandardCharsets

fun main(args: Array<String>) {
    val json = FeatureJSON()
    val geojsonString =
        "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-162.1,69.5],[-111.8,46.8],[-71.4,69.5],[-162.1,69.5]]]}}]}"
    val featureCollection = json.readFeatureCollection(
        geojsonString.byteInputStream(StandardCharsets.UTF_8)
    )
    assert(featureCollection.size() == 1)

    val polygon = featureCollection.features().next().defaultGeometryProperty.value as Polygon

    // Project WGS84 to UTM
    val sourceCrs = CRS.decode("EPSG:4326", true)
    val targetCrs = CRS.decode("AUTO:42001,${polygon.centroid.x},${polygon.centroid.y}")
    val transform = CRS.findMathTransform(sourceCrs, targetCrs)
    val projectedPolygon = JTS.transform(polygon, transform) as Polygon

    println("original  $polygon")
    println("projected $projectedPolygon")
    println("original-area  ${polygon.area}")
    println("projected-area ${projectedPolygon.area}")
}

Output:

original  POLYGON ((-162.1 69.5, -111.8 46.8, -71.4 69.5, -162.1 69.5))
projected POLYGON ((-1119988.9444535263 8348019.169233548, 896771.6764984068 5196081.82153673, 2134628.0450458853 8361900.673202005, -1119988.9444535263 8348019.169233548))
original-area  1029.445
projected-area 5.143172256107476E12

Here is the accompanying build.gradle:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.2.71'
}

version = '1.0-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    // Define repositories for geotools
    maven { url = "https://repo.boundlessgeo.com/main/" }
    maven { url = "http://maven.geomajas.org" }

    // default repo
    mavenCentral()
}

dependencies {
    implementation 'org.jetbrains.kotlin:kotlin-reflect'
    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'

    implementation group: 'org.geotools', name: 'gt-epsg-hsql', version: '21.1'
    implementation group: 'org.geotools', name: 'gt-epsg-extension', version: '21.1'
    implementation group: 'org.geotools', name: 'gt-geojson', version: '21.1'
}

compileKotlin {
    kotlinOptions {
        freeCompilerArgs = ['-Xjsr305=strict']
        jvmTarget = '1.8'
    }
}

compileTestKotlin {
    kotlinOptions {
        freeCompilerArgs = ['-Xjsr305=strict']
        jvmTarget = '1.8'
    }
}

But when running it with -enableassertions, it fails with:

Exception in thread "main" org.geotools.referencing.operation.projection.ProjectionException: The transform result may be 120,088.818 meters away from the expected position. Are you sure that the input coordinates are inside this map projection area of validity? The point is located 42°03.5'W away from the central meridian and 69°42.0'N away from the latitude of origin. The projection is "Transverse_Mercator".
    at org.geotools.referencing.operation.projection.MapProjection.checkReciprocal(MapProjection.java:705)
    at org.geotools.referencing.operation.projection.MapProjection.transform(MapProjection.java:905)
    at org.geotools.referencing.operation.projection.MapProjection.transform(MapProjection.java:940)
    at org.geotools.geometry.jts.DefaultCoordinateSequenceTransformer.transform(DefaultCoordinateSequenceTransformer.java:109)
    at org.geotools.geometry.jts.GeometryCoordinateSequenceTransformer.projectCoordinateSequence(GeometryCoordinateSequenceTransformer.java:275)
    at org.geotools.geometry.jts.GeometryCoordinateSequenceTransformer.transformStraightLineString(GeometryCoordinateSequenceTransformer.java:221)
    at org.geotools.geometry.jts.GeometryCoordinateSequenceTransformer.transformLineString(GeometryCoordinateSequenceTransformer.java:200)
    at org.geotools.geometry.jts.GeometryCoordinateSequenceTransformer.transformPolygon(GeometryCoordinateSequenceTransformer.java:283)
    at org.geotools.geometry.jts.GeometryCoordinateSequenceTransformer.transform(GeometryCoordinateSequenceTransformer.java:157)
    at org.geotools.geometry.jts.JTS.transform(JTS.java:449)
    at com.regions.MainKt.main(main.kt:24)

Any ideas what could cause this?

(If you like to test it on your own, here is the full minimal project as a download: https://www.dropbox.com/s/s7jj0x2dja15p25/kotlin_geojson_assertion_error.zip?dl=1)

closed as off-topic by Vince, whyzar, Fran Raga, xunilk, Jochen Schwarze May 20 at 7:10

This question appears to be off-topic. The users who voted to close gave this specific reason:

  • "Questions that relate to the business of vendors and product teams, such as licensing, pricing, release dates, submission of bug reports and enhancement requests, etc, are off-topic here and should be directed instead to the official software support teams." – Vince, whyzar, Fran Raga, xunilk, Jochen Schwarze
If this question can be reworded to fit the rules in the help center, please edit the question.

2

I think the assertion message reasonably explains the problem:

The transform result may be 120,088.818 meters away from the expected position. Are you sure that the input coordinates are inside this map projection area of validity? The point is located 42°03.5'W away from the central meridian and 69°42.0'N away from the latitude of origin. The projection is "Transverse_Mercator".

The UTM is not a single global projection, but a system that divides globe into 60 zones. Each zone has 6 degrees of longitude, where it minimize distortion. When you transform to "AUTO" UTM, you are transforming to one of the UTM zones, allowing the system to select appropriate zone. A single geographic feature can not span across multiple zones, and is assigned a single zone, although it is OK to span to neighbor zones a bit. But the feature you are trying to project spans from -162 to -71 degrees, and there is no single UTM zone where it is represented well enough.

What I think happens here: 1) you ask for AUTO zone, so the code chooses the appropriate UTM zone, probably based on some median longitude. 2) it then tries to convert each vertex to this UTM zone. 3) when converting, it finds the vertex is too far away from the zone, so conversion does not make sense, and this is the assertion that fails.

1

The most likely cause of this issue is that you have (inadvertently) swapped your lat/lon between the two runs. Without seeing the input to the spring run it's hard to be sure where the problem is occuring.

It could be that spring is picking up a different gt-epsg-X module and ESPG:4326 has a different axis order defined in it. Another potential issue is that (for some reason) CRS.decode("EPSG:4326") will give lat,lon:

GEOGCS["WGS 84", 
  DATUM["World Geodetic System 1984", 
    SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], 
    AUTHORITY["EPSG","6326"]], 
  PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
  UNIT["degree", 0.017453292519943295], 
  AXIS["Geodetic latitude", NORTH], 
  AXIS["Geodetic longitude", EAST], 
  AUTHORITY["EPSG","4326"]]

While DefaultGeographicCRS.WGS84 will give lon, lat:

GEOGCS["WGS84(DD)", 
  DATUM["WGS84", 
    SPHEROID["WGS84", 6378137.0, 298.257223563]], 
  PRIMEM["Greenwich", 0.0], 
  UNIT["degree", 0.017453292519943295], 
  AXIS["Geodetic longitude", EAST], 
  AXIS["Geodetic latitude", NORTH]]

So to be on the safe side I would always use something like:

if (CRS.getAxisOrder(s).equals(AxisOrder.EAST_NORTH)) {
    val targetCrs = CRS.decode("AUTO:42001,${polygon.centroid.x},${polygon.centroid.y}")
} else {
    val targetCrs = CRS.decode("AUTO:42001,${polygon.centroid.y},${polygon.centroid.x}")
}
  • Thanks a lot for the idea, and the explanation. However, I just tried it out, and the error is still the same. – Tobias Hermann May 17 at 17:07
  • Can you add the input point that is falling – Ian Turton May 17 at 17:32
  • With the debugger I stepped through the library, and in MatProjection.java in public final void transform(final double[] srcPts, int srcOff, final double[] dstPts, int dstOff, int numPts) already processing the first point ((-162.1, 69.5)) throws a ProjectionException. – Tobias Hermann May 17 at 18:27
  • can you see where your EPSG codes are coming from, and is the AUTO code the same for both runs? – Ian Turton May 18 at 10:51
  • Just checked by debugging into both versions, and all values are exactly the same. The difference is just that in MapProjection.java in public final Point2D transform(final Point2D ptSrc, Point2D ptDst) the assert checkReciprocal(ptDst, (ptSrc != ptDst) ? ptSrc : new Point2D.Double(x, y), true); is only invoked in the test run. The assert is skipped in the normal run. So it seems the problem has nothing to do with Spring or similar, it is just not recognized in the normal run. I'll adjust my question accordingly. – Tobias Hermann May 18 at 14:25
1

The explanation from Michael is really good. In case somebody would like to disable the sanity checks of the library because the use-case allows a bit or imprecision, it can be done like this:

import org.geotools.referencing.operation.projection.MapProjection

...

    MapProjection.SKIP_SANITY_CHECKS = true

Not the answer you're looking for? Browse other questions tagged or ask your own question.