OSGeo / PROJ

PROJ - Cartographic Projections and Coordinate Transformations Library
https://proj.org
Other
1.69k stars 770 forks source link

proj_create_crs_to_crs_from_pj returns NULL for valid CRS pair #4035

Closed vmirgorod closed 6 months ago

vmirgorod commented 6 months ago

Unable to create C-api proj from other PJs with message: proj_create_operations: target_crs is not a CRS or a CoordinateMetadata

    PJ_CONTEXT *context = proj_context_create();
    PJ *projection = proj_create(context, "EPSG:4326");
    PJ *another = proj_create(context, "+proj=lcc +lat_0=45 +lon_0=6.11666666699987 +lat_1=47 +lat_2=45 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs");
    PJ *nullCRSToCRS = proj_create_crs_to_crs_from_pj(context, projection, another, NULL, NULL);
    PJ *nonnullCRSToCRS = proj_create_crs_to_crs(context,  "EPSG:4326", "+proj=lcc +lat_0=45 +lon_0=6.11666666699987 +lat_1=47 +lat_2=45 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs", NULL);
    printf("1: %p, 2: %p", nullCRSToCRS, nonnullCRSToCRS); // 1: 0x0, 2: 0x7fa12800b930

Problem description

I believe there is some problem with applying crs type to PJ object.

Expected Output

Both nullCRSToCRS and nonnullCRSToCRS should be non null

Environment Information

Installation method

Notes

jjimenezshaw commented 6 months ago

Looking at this function that is called in proj_create_crs_to_crs

/** Adds a " +type=crs" suffix to a PROJ string (if it is a PROJ string) */
std::string pj_add_type_crs_if_needed(const std::string &str) {
    std::string ret(str);
    if ((starts_with(str, "proj=") || starts_with(str, "+proj=") ||
         starts_with(str, "+init=") || starts_with(str, "+title=")) &&
        str.find("type=crs") == std::string::npos) {
        ret += " +type=crs";
    }
    return ret;
}

you should specify the type to call proj_create

vmirgorod commented 6 months ago

Yes, probable, but should the behavior of these 2 functions be same? I meant if proj_create_crs_to_crs_from_pj returns null, proj_create_crs_to_crs should return null too.

jjimenezshaw commented 6 months ago

If you check the value of proj_get_type(another) you see that it is a PJ_TYPE_OTHER_COORDINATE_OPERATION, not a PJ_TYPE_..._CRS. I do not know if it is possible (and worth it) to "mutate" it into a CRS.

Anyhow it is a good idea to let it clear in your proj pipeline if you are talking about a crs or something else. It is probably not mandatory in proj_create_crs_to_crs to keep compatibility with old pipelines.

rouault commented 6 months ago

Anyhow it is a good idea to let it clear in your proj pipeline if you are talking about a crs or something else

That's the only resolution I can see. The function name asks for PJ* that are CRS. And if you don't provide one, there's an error message explaining you need to provide a CRS. We can hardly do more.

I do not know if it is possible (and worth it) to "mutate" it into a CRS.

Generally not, and it would be dangerous.

It is probably not mandatory in proj_create_crs_to_crs to keep compatibility with old pipelines.

I don't remember the details, but much likely.

vmirgorod commented 6 months ago

If you check the value of proj_get_type(another) you see that it is a PJ_TYPE_OTHER_COORDINATE_OPERATION, not a PJTYPE..._CRS. I do not know if it is possible (and worth it) to "mutate" it into a CRS.

Yes, I agreed that better to keep clear types (without any implicit mutation).

Anyhow it is a good idea to let it clear in your proj pipeline if you are talking about a crs or something else. It is probably not mandatory in proj_create_crs_to_crs to keep compatibility with old pipelines.

I think it is ok, along with clear message that +type=crs should be added to proj reference string

rouault commented 6 months ago

I think it is ok, along with clear message that +type=crs should be added to proj reference string

at the point where proj_create_crs_to_crs_from_pj() is called, it has no way to know that its arguments have been created from a PROJ.4 style string (that could a WKT string, a EPSG:12345 code, etc etc.), so we cannot provide such an explicit message

alexbejann commented 5 months ago

Hi @vmirgorod, could you share your approach of getting proj into iOS?

vmirgorod commented 5 months ago

@alexbejann yes, for building for iOS there are 2 possibilities: Either to create Xcode project with all proj header/source files and simply fix compiler errors (major issues is including non source files - they should be simply removed, as well as generated proj_config file). Method is good for debug, but you will have to rebuild your project manually on any proj side code changes.

Or to make bash/python script for automated building. You will need this toolchain for cmake: https://github.com/leetal/ios-cmake Bash script looks as follows. I omit part where proj sources are being downloaded from git. Major parts there are:

Note: if anybody want to compile to it to iOS framework / xcframework instead of static library, additional magic will take place.

Note: You will have to copy resources to app bundle and specify it in environment when using it on iOS side.

if let url = Bundle.main.url(for: "Proj.bundle") {
            setenv("PROJ_DATA", url.relativePath, 1)
            setenv("PROJ_NETWORK", "OFF", 1)
        } else {
            fatalError("Unable to locate Proj resources bundle")
        }
#!/bin/bash
set -e

# Please verify current CMD via xcode-select -p
# sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

PROJ_URL=https://download.osgeo.org/proj/proj-9.3.1.tar.gz

DIR=$PWD
TOOLCHAIN=${DIR}/ios.toolchain.cmake
CACHE=${DIR}/cache
BUILD=${DIR}/build

PROJ_FILE=$(basename -- "$PROJ_URL")
PROJ_DIR="${CACHE}/${PROJ_FILE%.tar.gz}"
PLATFORMS=("OS64" "SIMULATOR64" "SIMULATORARM64")

# Parameter is target platform
function build_slice() {
    local PLATFORM=$1
    local PREFIX=${BUILD}/$PLATFORM
    local PROJ_WORK=$${DIR}/work/proj-${PLATFORM}

    # Find out required sdk path
    case $PLATFORM in
      OS64)
        local SDKPATH=$(xcrun --sdk iphoneos --show-sdk-path);;

      SIMULATOR64 | SIMULATORARM64)
        local SDKPATH=$(xcrun --sdk iphonesimulator --show-sdk-path);;

      *)
        echo -n "Unknown platform $PLATFORM"
        exit 1
        ;;
    esac

    # Preparing directories
    mkdir -p ${BUILD}

    # Compiling PROJ4

    rm -rf "${PROJ_WORK}"
    mkdir -p "${PROJ_WORK}"
    cd "${PROJ_WORK}"

    cmake -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN \
        -DPLATFORM=$PLATFORM \
        -DENABLE_BITCODE=OFF \
        -DBUILD_SHARED_LIBS=OFF \
        -DCMAKE_INSTALL_PREFIX=$PREFIX \
        -DENABLE_TIFF=OFF -DENABLE_CURL=OFF \
        -DBUILD_PROJSYNC=OFF \
        -DSQLITE3_INCLUDE_DIR=$SDKPATH/usr/include \
        -DSQLITE3_LIBRARY=$SDKPATH/usr/lib/libsqlite3.tbd \
        "${PROJ_DIR}"

    cmake --build .
    cmake --build . --target install
}

# Building slices

for PLATFORM in ${PLATFORMS[@]}; do
    build_slice $PLATFORM
done