Re: AutoIdentifyEPSG() fails on standard ESRI-dialect NAD83/UTM .prj (proj_identify succeeds); FindMatches() also missing from Java bindings
Barry DeZonia via gdal-dev <[email protected]>
| Newsgroups | gmane.comp.gis.gdal.devel |
|---|---|
| Message-ID | <CAKcvfuQFcKDBZMyDFZHWWun_tO2AjKX9gk5vuk6F9yrqqhZ+fQ@mail.gmail.com> |
My foggy memory tells me that this looks good. I will let you and Even figure out the PR issue. On Thu, Aug 13, 2026 at 6:56 PM Tom Moore <[email protected]> wrote: > Thanks for offering to take a look. I have a working draft already, but > I'm not sure if I'm taking the right approach and could use a review. > > The signature translation itself is the easy part. The C# binding in > osr.i was an almost perfect example, and this is what I changed it to: > > #ifdef SWIGJAVA > OSRSpatialReferenceShadow** FindMatches( char** options, int* > nvalues, int** confidence_values ) > { > return (OSRSpatialReferenceShadow**) OSRFindMatches(self, > options, nvalues, confidence_values); > } > #endif > > Everything interesting (haha, challenging) turned out to be in > typemaps_java.i, getting SWIG to actually expose that return value and the > two output parameters sensibly in Java. I haven't used swig in a long > time, so I had forgotten most of the incantations. Llm to the rescue. > > Here is the addition to typemaps_java.i: > == start == > %typemap(in,numinputs=0) int* nvalues (int nMatches = 0) > { > /* %typemap(in,numinputs=0) int* nvalues */ > $1 = &nMatches; > } > > %typemap(in) int** confidence_values (int* panConfidence = NULL) > { > /* %typemap(in) int** confidence_values. Ignore the input, > keep $input around so argout can write the real > result into element [0]. */ > $1 = &panConfidence; > } > > %typemap(argout) int** confidence_values > { > /* %typemap(argout) int** confidence_values will fill the caller's > int[][] */ > if ($input && jenv->GetArrayLength($input) >= 1) { > jintArray confArray = jenv->NewIntArray(nMatches3); > jenv->SetIntArrayRegion(confArray, 0, nMatches3, > (jint*)panConfidence4); > jenv->SetObjectArrayElement($input, 0, confArray); > jenv->DeleteLocalRef(confArray); > } > CPLFree(panConfidence4); > } > > %typemap(jni) int** confidence_values "jobjectArray" > %typemap(jtype) int** confidence_values "int[][]" > %typemap(jstype) int** confidence_values "int[][]" > %typemap(javain) int** confidence_values "$javainput" > > %typemap(out) (OSRSpatialReferenceShadow**) > { > const jclass srsClass = jenv->FindClass("org/gdal/osr/SpatialReference"); > const jmethodID srsCtor = jenv->GetMethodID(srsClass, "<init>", "(JZ)V"); > > jresult = jenv->NewObjectArray(nMatches3, srsClass, NULL); > for (int i = 0; i < nMatches3; i++) { > OSRReference(result[i]); > jobject srsObj = jenv->NewObject(srsClass, srsCtor, (jlong)result[i], > (jboolean)true); > jenv->SetObjectArrayElement(jresult, i, srsObj); > jenv->DeleteLocalRef(srsObj); > } > OSRFreeSRSArray(result); > } > > %typemap(jni) OSRSpatialReferenceShadow** "jobjectArray" > %typemap(jtype) OSRSpatialReferenceShadow** > "org.gdal.osr.SpatialReference[]" > %typemap(jstype) OSRSpatialReferenceShadow** > "org.gdal.osr.SpatialReference[]" > %typemap(javaout) OSRSpatialReferenceShadow** { > return $jnicall; > } > == end == > > The nvalues argument stays hidden (numinputs=0), while confidence_values > becomes visible as an int[][] out-box, following the same > caller-passes-a-1-element-array-and-we-fill-it idiom already used elsewhere > in typemaps_java.i (the GDALDimensionHS** pattern was the model here). > > SWIG auto-suffixes typemap-declared local variables with the argument's > position in the full parameter list to avoid collisions. FOr example, a > variable I declared as (int nMatches = 0) inside the nvalues typemap > (argument position 3) actually gets emitted into the generated code as > nMatches3, not nMatches. This isn't visible from the .i source at all, I > only found it by examining the generated osr_wrap.cpp after a failed > compile. Any other typemap block that needs to reference that same variable > (the argout/out blocks) has to hardcode the anticipated suffixed name > directly because SWIG doesn't rewrite references for you. > > End result: > > public SpatialReference[] FindMatches(Vector options, int[][] > confidenceValuesOut) > > The function returns an array of matches (SpatialReference[] as > OSRSpatialReferenceShadow**), which is the primary thing that you want to > get from this function. > > How to use: > int[][] confidence = new int[1][]; > SpatialReference[] matches = srs.FindMatches(null, confidence); > int[] confidenceValues = confidence[0]; > > Patched against the current master. Tested on against an esri shapefile > .prj (ESRI-dialect NAD83 / UTM zone 11N WKT) that AutoIdentifyEPSG() fails > on with 'Unsupported SRS' -- FindMatches() correctly returns a single > match, EPSG:26911, with confidence[0][0] = 100, matching projinfo > --identify exactly on the same input. Not tested any further than that. > > How does this look so far? Is this going in the right direction? If so, > what would it take to turn this into an acceptable PR? > > Thanks again for your help, and full disclosure, thanks to llm for > significant assistance on this. > Tom > > On Thu, Aug 13, 2026, at 5:16 PM, Barry DeZonia wrote: > > AI is helping me remember > > for the FindMatches() routine would you want it matching as close as > possible C types or would you want a simplified API for Java that mogt > communicate info via an array of strings (or one formatted string). Sorry > if that sounds out of left field. > > One AI said this: > 2. Handle Java JNI Typemaps > Because int** and object arrays (OSRSpatialReferenceShadow***) do not map > cleanly to standard Java types, you must ensure your SWIG configuration or > a companion helper helper translates them into manageable objects on the > Java side. > Alternatively, if dealing with direct pointer manipulation via SWIG > typemaps is too complex, developers routinely implement a *custom C++ > wrapper function* inside osr.i that bundles the results into a string > format (such as an array of matching EPSG strings and confidences) before > passing it over the JNI boundary: > > swig > > %extend OSRSpatialReferenceShadow { > // A simplified wrapper returning matches as formatted string arrays to avoid pointer hell > char** FindMatchesSimple(char** options) { > int nEntries = 0; > int* panConfidence = NULL; > OGRSpatialReferenceH* pahSRS = OGRSpatialReference_FindMatches(self, options, &nEntries, &panConfidence); > > char** papszResult = NULL; > // Construct a structured string array containing "EPSG:Code,Confidence" > for(int i = 0; i < nEntries; i++) { > const char* pszAuthName = OGRSpatialReference_GetAttrValue(pahSRS[i], "AUTHORITY", 0); > const char* pszAuthCode = OGRSpatialReference_GetAttrValue(pahSRS[i], "AUTHORITY", 1); > // Format string logic, append to papszResult... > // BDZ - TODO - NOTE - This is the real work needed to finish this method. > } > > OSRFreeSRSArray(pahSRS); > CPLFree(panConfidence); > return papszResult; > } > } > > > On Thu, Aug 13, 2026 at 3:08 PM Barry DeZonia <[email protected]> wrote: > > I could maybe help here but, good god, it's been a long time since I've > worked on the Java bindings and I remember almost nothing. Maybe if I > inspect the Java bindings code I will remember some stuff. Naively, the > function signature looks pretty simple to translate. > > On Thu, Aug 13, 2026 at 6:53 AM Even Rouault via gdal-dev < > [email protected]> wrote: > > > Tom, > > yes FindMatches() is what is needed for your use case and it is not > currently available in the Java bindings. That would require someone to > write the appropriate typemap(s) to map the C types to Java types. > > Even > Le 13/08/2026 à 13:15, Tom Moore via gdal-dev a écrit : > > Hi all, > > I'm running into a case where SpatialReference.AutoIdentifyEPSG() fails on > a completely standard, well-formed ESRI-dialect .prj file, even though the > underlying PROJ identification machinery (proj_identify(), as exercised via > "projinfo --identify") resolves it correctly with 100% confidence. I'd like > to know whether this is a known limitation, and whether there's a > recommended workaround for Java bindings users specifically, since > FindMatches() isn't exposed there. > > Environment: > - GDAL 3.11.1 (Windows build) > - PROJ 9.6.2 > - Java bindings (org.gdal.osr / SWIG) > > Input WKT (contents of a shapefile .prj, ESRI dialect): > > > PROJCS["NAD_1983_UTM_Zone_11N",GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",-117.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]] > > QGIS correctly identifies this as EPSG:26911. > > What I tried (Java): > > SpatialReference srs = new SpatialReference(); > srs.ImportFromESRI(lines); // also tried plain ImportFromWkt() but > got the same result > srs.SetAxisMappingStrategy(osrConstants.OAMS_TRADITIONAL_GIS_ORDER); > > try { > srs.AutoIdentifyEPSG(); > } catch (Exception ex) { > System.out.println("Exception: " + ex.getMessage()); > } > System.out.println("code = " + srs.GetAuthorityCode(null)); > > Result: > > Exception: OGR Error: Unsupported SRS > code = null > > gdal.GetLastErrorType() / GetLastErrorMsg() are empty. No additional > diagnostic detail is surfaced beyond the generic exception. > > I ruled out several things before suspecting this is a AutoIdentifyEPSG() > limitation rather than an environment/config issue on my end: > > 1. proj.db itself is correct. Confirmed by mounting the exact same > proj.db (from the PROJ 9 data directory used by my JAva app) into a clean > ghcr.io/osgeo/gdal:ubuntu-small-latest container and running `gdalsrsinfo > -e` against the same .prj file. It resolved to EPSG:26911 correctly, full > WKT2 with all authority IDs. > 2. PROJ_LIB / PROJ_DATA environment variables are set correctly and > confirmed using System.getenv() to be visible to the process > 3. Tried MorphFromESRI() (both via ImportFromESRI() alone, and combined > with an explicit MorphFromESRI() call) - no change. > 4. Tried explicit SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER) > before calling AutoIdentifyEPSG() - no change. > 5. Ran the projinfo.exe from the gdal build that I am using with the Java > app directly against the same .prj file: > > projinfo.exe --identify fmu.prj > ... > Identification match count: 1 > EPSG:26911: 100 % > > So proj_identify() / FindMatches()-equivalent logic resolves this WKT > perfectly in the exact same native build, but AutoIdentifyEPSG() fails on > it. > > This looks consistent with a few things I found in the tracker/mailing > list archives while investigating: > > - https://trac.osgeo.org/gdal/ticket/6188 -- maintainer comment noting > AutoIdentifyEPSG() has "very limited capabilities" and would need fuzzy > matching to handle inexact datum/ellipsoid names, "especially when dealing > with WKT coming from ESRI." > - https://github.com/OSGeo/gdal/issues/4038 -- near-identical repro WKT > (ESRI-dialect UTM), with a maintainer noting AutoIdentifyEPSG() can return > OGRERR_UNSUPPORTED_SRS while having still injected partial AUTHORITY tags > into sub-nodes. This matches what I see when printing the WKT after the > failed call (DATUM gets EPSG:6269, but the top-level PROJCS never gets an > ID). > - https://github.com/OSGeo/gdal/issues/2303 -- shows the standard Python > workaround (fall back to FindMatches() when AutoIdentifyEPSG() fails), > which leads to my second question below. > > Questions: > > 1. Is AutoIdentifyEPSG() failing on this ESRI-dialect NAD83/UTM WKT a > known limitation, or does this look like a genuine regression/bug worth > filing? > 2. FindMatches() does not appear to be exposed in the Java SWIG bindings > (org.gdal.osr.SpatialReference). Is that correct, or am I missing > something? If it's genuinely absent, is there any appetite for adding it, > given it's the documented fallback for exactly this situation in other > language bindings? > 3. Short of shelling out to "projinfo --identify" as a subprocess, is > there a recommended in-process approach for Java bindings users to reach > the same identification logic? > > Happy to provide a minimal reproducible test case / file a tracker issue > if that's useful, but I wanted to check here first in case this is already > understood behavior. > > Thanks, > Tom > > > _______________________________________________ > gdal-dev mailing [email protected]://lists.osgeo.org/mailman/listinfo/gdal-dev > > -- http://www.spatialys.com > My software is free, but my time generally not. > LLMs contribute to global warming and brain rot. > Let's guillotine them! "Ah ! ça ira, ça ira, ça ira !" > > _______________________________________________ > gdal-dev mailing list > [email protected] > https://lists.osgeo.org/mailman/listinfo/gdal-dev > > > -- > Tom Moore > Spatial Planning Systems > 960 Burkes Bluff Lane > Deep River ON K0J 1P0 > Canada > > Phone: +1 613 584 9354 > > _______________________________________________ gdal-dev mailing list [email protected] https://lists.osgeo.org/mailman/listinfo/gdal-dev