/*------------------------------------------------------------------------- * * oracle_gis.c * routines that convert between Oracle SDO_GEOMETRY and PostGIS EWKB * *------------------------------------------------------------------------- */ /* * The code relies heavily on the PostGIS internal data stucture that is explained * in g_serialized.txt in the PostGIS source code and implemented in liblwgeom.h. */ /* Oracle header */ #include #include #include #include #include #include "oracle_fdw.h" #define POINTTYPE 1 #define LINETYPE 2 #define POLYGONTYPE 3 #define MULTIPOINTTYPE 4 #define MULTILINETYPE 5 #define MULTIPOLYGONTYPE 6 #define COLLECTIONTYPE 7 #define CIRCSTRINGTYPE 8 #define COMPOUNDTYPE 9 #define CURVEPOLYTYPE 10 #define MULTICURVETYPE 11 #define MULTISURFACETYPE 12 #define POLYHEDRALSURFACETYPE 13 #define TRIANGLETYPE 14 #define TINTYPE 15 #define WKBSRIDFLAG 0x20000000 #define WKBZOFFSET 0x80000000 #define WKBMOFFSET 0x40000000 #define uintToNumber(errhp, intp, numberp) { \ if (checkerr( \ OCINumberFromInt((errhp), (dvoid *)(intp), sizeof(unsigned), OCI_NUMBER_UNSIGNED, (numberp)), \ (errhp)) != OCI_SUCCESS) \ oracleError_d(FDW_ERROR, "OCINumberFromInt failed to convert integer to NUMBER", oraMessage); \ } #define numberToUint(errhp, numberp, intp) { \ if (checkerr( \ OCINumberToInt((errhp), (numberp), sizeof(unsigned), OCI_NUMBER_UNSIGNED, (dvoid *)(intp)), \ (errhp)) != OCI_SUCCESS) \ oracleError_d(FDW_ERROR, "OCINumberToInt failed to convert NUMBER to integer", oraMessage); \ } #define doubleToNumber(errhp, doublep, numberp) { \ if (checkerr( \ OCINumberFromReal((errhp), (const dvoid *)(doublep), sizeof(double), (numberp)), \ (errhp)) != OCI_SUCCESS) \ oracleError_d(FDW_ERROR, "OCINumberFromReal failed to convert floating point number to NUMBER", oraMessage); \ } #define numberToDouble(errhp, numberp, doublep) { \ if (checkerr( \ OCINumberToReal((errhp), (numberp), sizeof(double), (dvoid *)(doublep)), \ (errhp)) != OCI_SUCCESS) \ oracleError_d(FDW_ERROR, "OCINumberToReal failed to convert NUMBER to floating point number", oraMessage); \ } /* file for mapping SRIDs in the "share" directory */ #define SRID_MAP_FILE "srid.map" typedef struct { unsigned from; /* != 0 for valid ones */ unsigned to; } mapEntry; #define mapEntryValid(x) ((x)->from != 0) /* maps Oracle SRIDs to PostGIS SRIDs */ static mapEntry *srid_map = NULL; /* contains Oracle error messages, set by checkerr() */ #define ERRBUFSIZE 500 static char oraMessage[ERRBUFSIZE]; static sb4 err_code; /* * Structures needed for managing the MDSYS.SDO_GEOMETRY Oracle type. * Most of these were generated with OTT. */ typedef struct { OCINumber x; OCINumber y; OCINumber z; } sdo_point_type; typedef struct { OCIInd _atomic; OCIInd x; OCIInd y; OCIInd z; } sdo_point_type_ind; typedef struct sdo_geometry { OCINumber sdo_gtype; OCINumber sdo_srid; sdo_point_type sdo_point; OCIArray *sdo_elem_info; OCIArray *sdo_ordinates; } sdo_geometry; typedef struct sdo_geometry_ind { OCIInd _atomic; OCIInd sdo_gtype; OCIInd sdo_srid; sdo_point_type_ind sdo_point; OCIInd sdo_elem_info; OCIInd sdo_ordinates; } sdo_geometry_ind; static unsigned ewkbType(oracleSession *session, ora_geometry *geom); static unsigned sdoDimension(oracleSession *session, ora_geometry *geom); static unsigned ewkbIsMeasured(oracleSession *session, ora_geometry *geom); static unsigned ewkbSrid(oracleSession *session, ora_geometry *geom); static unsigned numCoord(oracleSession *session, ora_geometry *geom); static double coord(oracleSession *session, ora_geometry *geom, unsigned i); static unsigned numElemInfo(oracleSession *session, ora_geometry *geom); static unsigned elemInfo(oracleSession *session, ora_geometry *geom, unsigned i); static void appendElemInfo(oracleSession *session, ora_geometry *geom, unsigned info ); static void appendCoord(oracleSession *session, ora_geometry *geom, double coord); static char *doubleFill(double x, char * dest); static char *unsignedFill(unsigned i, char * dest); static sword checkerr(sword status, OCIError *handle); static void initSRIDMap(void); static unsigned epsgFromOracle(unsigned srid); static unsigned epsgToOracle(unsigned srid); static void unpack(oracleSession *session, ora_geometry *geom); static void freeUnpacked(oracleSession *session, ora_geometry *geom); /* All ...Fill() functions return a pointer to the end of the written zone */ static unsigned ewkbHeaderLen(oracleSession *session, ora_geometry *geom); static char *ewkbHeaderFill(oracleSession *session, ora_geometry *geom, char * dest); static unsigned ewkbPointLen(oracleSession *session, ora_geometry *geom); static char *ewkbPointFill(oracleSession *session, ora_geometry *geom, char *dest); static unsigned ewkbLineLen(oracleSession *session, ora_geometry *geom); static char *ewkbLineFill(oracleSession *session, ora_geometry *geom, char * dest); static unsigned ewkbPolygonLen(oracleSession *session, ora_geometry *geom); static char *ewkbPolygonFill(oracleSession *session, ora_geometry *geom, char * dest); static unsigned ewkbMultiPointLen(oracleSession *session, ora_geometry *geom); static char *ewkbMultiPointFill(oracleSession *session, ora_geometry *geom, char *dest); static unsigned ewkbMultiLineLen(oracleSession *session, ora_geometry *geom); static char *ewkbMultiLineFill(oracleSession *session, ora_geometry *geom, char * dest); static unsigned ewkbMultiPolygonLen(oracleSession *session, ora_geometry *geom); static char *ewkbMultiPolygonFill(oracleSession *session, ora_geometry *geom, char * dest); /* * All the set...() functions return a pointer that points to a position in the * input buffer right after the added data. * That way we can call several of them in a row and even nest them, * like in the case of a multipolygon composed of several polygons. */ static const char *setType(oracleSession *session, ora_geometry *geom, const char *data); static const char *setSridAndFlags(oracleSession *session, ora_geometry *geom, const char *data); static const char *setPoint(oracleSession *session, ora_geometry *geom, const char *data); static const char *setLine(oracleSession *session, ora_geometry *geom, const char *data); static const char *setPolygon(oracleSession *session, ora_geometry *geom, const char *data); static const char *setMultiPoint(oracleSession *session, ora_geometry *geom, const char *data); static const char *setMultiLine(oracleSession *session, ora_geometry *geom, const char *data); static const char *setMultiPolygon(oracleSession *session, ora_geometry *geom, const char *data); char * doubleFill(double x, char * dest) { memcpy(dest, &x, sizeof(double)); dest += sizeof(double); return dest; } char * unsignedFill(unsigned i, char * dest) { memcpy(dest, &i, sizeof(unsigned)); dest += sizeof(unsigned); return dest; } /* * initSRIDMap * Allocates "srid_map" and reads the SRID map file into it. */ void initSRIDMap() { char *mapFileName = NULL; FILE *mapFile; char line[20]; unsigned long from, to; int count = 0, i, save_errno, c; mapFileName = oracleGetShareFileName(SRID_MAP_FILE); /* initialize "srid_map" with an invalid entry */ srid_map = (mapEntry *)malloc(sizeof(mapEntry)); if (srid_map == NULL) oracleError_i(FDW_ERROR, "failed to allocate %d bytes of memory", sizeof(mapEntry)); srid_map[0].from = 0; /* from here on we must make sure that srid_map is reset to NULL if an error occurs */ errno = 0; if ((mapFile = fopen(mapFileName, "r")) == NULL) { /* if the file does not exist, treat it as if it were empty */ if (errno == ENOENT) return; /* other errors are reported */ free(srid_map); srid_map = NULL; oracleError(FDW_ERROR, "cannot open file \"" SRID_MAP_FILE "\": %m"); } /* from here on we must make sure that mapFile is closed if an error happens */ oracleFree(mapFileName); do { /* read the next line into "line" */ i = 0; do { c = fgetc(mapFile); if (c == '\n' || c == EOF) { line[i] = '\0'; } else { if (i >= 19) { (void)fclose(mapFile); free(srid_map); srid_map = NULL; oracleError(FDW_ERROR, "syntax error in file \"" SRID_MAP_FILE "\": line too long"); } line[i++] = c; } } while (c != '\n' && c != EOF); /* ignore empty lines */ if (*line == '\0') continue; /* read two unsigned integers */ i = sscanf(line, "%lu %lu", &from, &to); if (i == EOF) { save_errno = errno; (void)fclose(mapFile); errno = save_errno; free(srid_map); srid_map = NULL; oracleError(FDW_ERROR, "syntax error in file \"" SRID_MAP_FILE "\": %m"); } if (i != 2) { (void)fclose(mapFile); free(srid_map); srid_map = NULL; oracleError(FDW_ERROR, "syntax error in file \"" SRID_MAP_FILE "\": line does not contain two numbers"); } if (from == 0 || to == 0) { (void)fclose(mapFile); free(srid_map); srid_map = NULL; oracleError(FDW_ERROR, "syntax error in file \"" SRID_MAP_FILE "\": SRID cannot be zero"); } if (from > 0xffffffff || to > 0xffffffff) { (void)fclose(mapFile); free(srid_map); srid_map = NULL; oracleError(FDW_ERROR, "syntax error in file \"" SRID_MAP_FILE "\": number too large"); } /* add a new mapEntry to srid_map */ srid_map = (mapEntry *)realloc(srid_map, sizeof(mapEntry) * (++count + 1)); if (srid_map == NULL) { (void)fclose(mapFile); free(srid_map); srid_map = NULL; oracleError_i(FDW_ERROR, "failed to allocate %d bytes of memory", sizeof(mapEntry) * (count + 1)); } srid_map[count - 1].from = (unsigned)from; srid_map[count - 1].to = (unsigned)to; srid_map[count].from = 0; } while (c != EOF); /* check for errors */ save_errno = errno; (void)fclose(mapFile); errno = save_errno; if (errno) { free(srid_map); srid_map = NULL; oracleError(FDW_ERROR, "error reading from file \"" SRID_MAP_FILE "\": %m"); } } unsigned epsgFromOracle(unsigned srid) { mapEntry *entry; if (srid_map == NULL) initSRIDMap(); for (entry = srid_map; mapEntryValid(entry); ++entry) if (entry->from == srid) return entry->to; return srid; } unsigned epsgToOracle(unsigned srid) { mapEntry *entry; if (srid_map == NULL) initSRIDMap(); for (entry = srid_map; mapEntryValid(entry); ++entry) if (entry->to == srid) return entry->from; return srid; } /* * oracleEWKBToGeom * Creates an Oracle SDO_GEOMETRY from a PostGIS EWKB. * The result is a partially palloc'ed structure. * Zero length or a NULL pointer for ewkb_data yield an atomically NULL object. */ ora_geometry * oracleEWKBToGeom(oracleSession *session, unsigned ewkb_length, char *ewkb_data) { const char *data = ewkb_data; unsigned type; ora_geometry *geom = (ora_geometry *)oracleAlloc(sizeof(ora_geometry)); oracleGeometryAlloc(session, geom); geom->num_elems = -1; geom->elem = NULL; geom->num_coords = -1; geom->coord = NULL; /* for NULL data, return an object that is atomically NULL */ if (data == NULL || ewkb_length == 0) return geom; else geom->indicator->_atomic = OCI_IND_NOTNULL; data = setSridAndFlags(session, geom, data); /* * We don't move the data pointer after this call because * it will be moved after the following setTYPE functions * and those functions expect the data pointer to be on the * type and not after (see comment above about g_serialized.txt * and set... functions). */ setType(session, geom, data); type = ewkbType(session, geom); /* these will be NULL for points */ geom->indicator->sdo_ordinates = (type == POINTTYPE) ? OCI_IND_NULL : OCI_IND_NOTNULL; geom->indicator->sdo_elem_info = (type == POINTTYPE) ? OCI_IND_NULL : OCI_IND_NOTNULL; switch (type) { case POINTTYPE: data = setPoint(session, geom, data); break; case LINETYPE: data = setLine(session, geom, data); break; case POLYGONTYPE: data = setPolygon(session, geom, data); break; case MULTIPOINTTYPE: data = setMultiPoint(session, geom, data); break; case MULTILINETYPE: data = setMultiLine(session, geom, data); break; case MULTIPOLYGONTYPE: data = setMultiPolygon(session, geom, data); break; default: oracleError_i(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: unexpected geometry type %u", type); } /* check that we reached the end of input data */ if (data - ewkb_data != ewkb_length) oracleError_ii(FDW_ERROR, "oracle_fdw internal error: number of bytes read %u is different from length %u", data - ewkb_data, ewkb_length); return geom; } /* * oracleGetEWKBLen * Returns the length in bytes needed to store an EWKB conversion of "geom". */ unsigned oracleGetEWKBLen(oracleSession *session, ora_geometry *geom) { unsigned type; /* return zero length for atomically NULL objects */ if (geom->indicator->_atomic == OCI_IND_NULL) return 0; /* unpack SDO_ELEM_INFO and SDO_ORDINATES */ if (geom->num_elems == -1) unpack(session, geom); /* a first check for supported types is done in ewkbType */ type = ewkbType(session, geom); /* * If sdo_elem_info is NOT NULL, we go through and check that the * type and interpretation are actually supported. */ if (geom->indicator->sdo_elem_info == OCI_IND_NOTNULL) { const unsigned n = geom->num_elems; unsigned i; for (i=0; ielem[i+1]; const unsigned interpretation = geom->elem[i+2]; if (!((1 == etype && 1 == interpretation) ||(2 == etype && 1 == interpretation) ||(1003 == etype && 1 == interpretation) ||(2003 == etype && 1 == interpretation) )) oracleError_ii(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: unsupported etype %u with interpretation %u in elem_info", etype, interpretation); } } switch (type) { case POINTTYPE: return ewkbHeaderLen(session, geom) + ewkbPointLen(session, geom); case LINETYPE: return ewkbHeaderLen(session, geom) + ewkbLineLen(session, geom); case POLYGONTYPE: return ewkbHeaderLen(session, geom) + ewkbPolygonLen(session, geom); case MULTIPOINTTYPE: return ewkbHeaderLen(session, geom) + ewkbMultiPointLen(session, geom); case MULTILINETYPE: return ewkbHeaderLen(session, geom) + ewkbMultiLineLen(session, geom); case MULTIPOLYGONTYPE: return ewkbHeaderLen(session, geom) + ewkbMultiPolygonLen(session, geom); default: oracleError_i(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: unexpected geometry type %u", type); return 0; /* unreachable, but keeps compiler happy */ } } /* * oracleFillEWKB * Converts "geom" to an EWKB and stores the result in "dest". */ char * oracleFillEWKB(oracleSession *session, ora_geometry *geom, unsigned size, char *dest) { const char *orig = dest; unsigned type; dest = ewkbHeaderFill(session, geom, dest); switch ((type = ewkbType(session, geom))) { case POINTTYPE: dest = ewkbPointFill(session, geom, dest); break; case LINETYPE: dest = ewkbLineFill(session, geom, dest); break; case POLYGONTYPE: dest = ewkbPolygonFill(session, geom, dest); break; case MULTIPOINTTYPE: dest = ewkbMultiPointFill(session, geom, dest); break; case MULTILINETYPE: dest = ewkbMultiLineFill(session, geom, dest); break; case MULTIPOLYGONTYPE: dest = ewkbMultiPolygonFill(session, geom, dest); break; default: oracleError_i(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: unexpected geometry type %u", type); } /* check that we have reached the end of the input buffer */ if (dest - orig != size) oracleError_ii(FDW_ERROR, "oracle_fdw internal error: number of bytes written %u is different from size %u", dest - orig, size); /* free memory in unpacked geometry */ if (geom->num_elems != -1) freeUnpacked(session, geom); return dest; } /* * oracleGeometryFree * Free the memory allocated with a geometry object. */ void oracleGeometryFree(oracleSession *session, ora_geometry *geom) { /* * From the OCI documentation: * If there is a top-level object (as with a non-atomically NULL object), * then the indicator is freed when the top-level object is freed with OCIObjectFree(). * If the object is atomically null, then there is no top-level object, * so the indicator must be freed separately. */ if (geom->geometry != NULL && geom->indicator->_atomic == OCI_IND_NOTNULL) (void)OCIObjectFree(session->envp->envhp, session->envp->errhp, geom->geometry, 0); else (void)OCIObjectFree(session->envp->envhp, session->envp->errhp, geom->indicator, 0); geom->geometry = NULL; geom->indicator = NULL; } /* * oracleGeometryAlloc * Allocate memory for a geometry object in the Oracle object cache. * The indicator is set to atomic NULL. */ void oracleGeometryAlloc(oracleSession *session, ora_geometry *geom) { /* allocate a SDO_GEOMETRY object */ if (checkerr( OCIObjectNew(session->envp->envhp, session->envp->errhp, session->connp->svchp, OCI_TYPECODE_OBJECT, oracleGetGeometryType(session), (dvoid *)NULL, OCI_DURATION_TRANS, TRUE, (dvoid **)&geom->geometry), session->envp->errhp) != OCI_SUCCESS) oracleError_d(FDW_ERROR, "cannot allocate SDO_GEOMETRY object", oraMessage); /* get the NULL indicator */ if (checkerr( OCIObjectGetInd(session->envp->envhp, session->envp->errhp, geom->geometry, (void **)&geom->indicator), session->envp->errhp) != OCI_SUCCESS) oracleError_d(FDW_ERROR, "cannot get indicator for new SDO_GEOMETRY object", oraMessage); /* initialize as atomic NULL */ geom->indicator->_atomic = OCI_IND_NULL; } void appendElemInfo(oracleSession *session, ora_geometry *geom, unsigned info) { OCINumber n; uintToNumber(session->envp->errhp, &info, &n); if (checkerr( OCICollAppend(session->envp->envhp, session->envp->errhp, (CONST dvoid*) &n, NULL, geom->geometry->sdo_elem_info), session->envp->errhp) != OCI_SUCCESS) oracleError_d(FDW_ERROR, "cannot append to element info collection", oraMessage); } void appendCoord(oracleSession *session, ora_geometry *geom, double coord) { OCINumber n; doubleToNumber(session->envp->errhp, &coord, &n); if (checkerr( OCICollAppend(session->envp->envhp, session->envp->errhp, (CONST dvoid*) &n, NULL, geom->geometry->sdo_ordinates), session->envp->errhp) != OCI_SUCCESS) oracleError_d(FDW_ERROR, "cannot append to ordinate collection", oraMessage); } unsigned ewkbType(oracleSession *session, ora_geometry *geom) { unsigned gtype = 0; if (geom->indicator->sdo_gtype == OCI_IND_NULL) oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: geometry type cannot be NULL"); numberToUint(session->envp->errhp, &(geom->geometry->sdo_gtype), >ype); switch (gtype%100) { case 1: return POINTTYPE; case 2: return LINETYPE; case 3: return POLYGONTYPE; case 4: oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: geometry type COLLECTION not supported"); break; case 5: return MULTIPOINTTYPE; case 6: return MULTILINETYPE; case 7: return MULTIPOLYGONTYPE; case 8: oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: geometry type SOLID not supported"); break; case 9: oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: geometry type MULTISOLID not supported"); break; default: oracleError_i(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: unknown geometry type %u", gtype); } return 0; /* unreachable, but keeps compiler happy */ } const char * setType(oracleSession *session, ora_geometry *geom, const char *data) { const ub4 wkbType = *((ub4 *)data); unsigned gtype; numberToUint(session->envp->errhp, &(geom->geometry->sdo_gtype), >ype); data += sizeof(ub4); switch (wkbType) { case POINTTYPE: gtype += 1; break; case LINETYPE: gtype += 2; break; case POLYGONTYPE: gtype += 3; break; case MULTIPOINTTYPE: gtype += 5; break; case MULTILINETYPE: gtype += 6; break; case MULTIPOLYGONTYPE: gtype += 7; break; #define UNSUPPORTED_TYPE( T ) case T ## TYPE: oracleError(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: geometry type "#T" not supported"); break; UNSUPPORTED_TYPE(COLLECTION) UNSUPPORTED_TYPE(CIRCSTRING) UNSUPPORTED_TYPE(COMPOUND) UNSUPPORTED_TYPE(CURVEPOLY) UNSUPPORTED_TYPE(MULTICURVE) UNSUPPORTED_TYPE(MULTISURFACE) UNSUPPORTED_TYPE(POLYHEDRALSURFACE) UNSUPPORTED_TYPE(TRIANGLE) UNSUPPORTED_TYPE(TIN) #undef UNSUPPORTED_TYPE default: oracleError_i(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: unknown geometry type %u", wkbType); } geom->indicator->sdo_gtype = OCI_IND_NOTNULL; uintToNumber(session->envp->errhp, >ype, &(geom->geometry->sdo_gtype)); return data; } /* * Header contains: * - srid : 3 bytes * - flags : 1 byte */ unsigned ewkbHeaderLen(oracleSession *session, ora_geometry *geom) { return 4; } char * ewkbHeaderFill(oracleSession *session, ora_geometry *geom, char * dest) { const unsigned srid = ewkbSrid(session, geom); const unsigned sdoDim = sdoDimension(session, geom); ub1 flags; ub1 s[3]; if (sdoDim > 3) oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: four-dimensional geometries are unsupported"); if (ewkbIsMeasured(session, geom)) /* * In Oracle, the measure dimension counts as third dimension, * so "sdoDim" will be 3. This corresponds to a two-dimensional * PostGIS geometry with the "measure" flag set. */ flags = 0x02; else flags = ((3 == sdoDim) ? 0x01 : 0x00 ); s[0] = (srid & 0x001F0000) >> 16; s[1] = (srid & 0x0000FF00) >> 8; s[2] = (srid & 0x000000FF); memcpy(dest, s, 3); dest += 3; memcpy(dest, &flags, 1); dest += 1; return dest; } unsigned ewkbIsMeasured(oracleSession *session, ora_geometry *geom) { unsigned gtype = 0; if (geom->indicator->sdo_gtype == OCI_IND_NOTNULL) numberToUint(session->envp->errhp, &(geom->geometry->sdo_gtype), >ype); switch ((gtype % 1000) / 100) { case 0: return 0; case 3: return 1; default: oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: measure dimension must be \"0\" or \"3\""); return 0; /* keep the compiler happy */ } } /* * sdoDimension * Returns the dimension as Oracle understands it. * For a measured geometry with two dimensions and one measure dimension, * it will return 3 (PostGIS considers such an object two-dimensional). */ unsigned sdoDimension(oracleSession *session, ora_geometry *geom) { unsigned gtype = 0; if (geom->indicator->sdo_gtype == OCI_IND_NOTNULL) numberToUint(session->envp->errhp, &(geom->geometry->sdo_gtype), >ype); return gtype / 1000; } unsigned ewkbSrid(oracleSession *session, ora_geometry *geom) { unsigned srid = 0; if (geom->indicator->sdo_srid == OCI_IND_NOTNULL) numberToUint(session->envp->errhp, &(geom->geometry->sdo_srid), &srid); /* convert Oracle->PostGIS SRID when needed */ return epsgFromOracle(srid); } const char * setSridAndFlags(oracleSession *session, ora_geometry *geom, const char *data) { unsigned srid = 0; unsigned gtype = 0; srid |= ((ub1)data[0]) << 16; srid |= ((ub1)data[1]) << 8; srid |= ((ub1)data[2]); /* * Only the first 21 bits are set. Slide up and back to pull * the negative bits down, if we need them. */ srid = (srid<<11)>>11; data += 3; /* convert PostGIS->Oracle SRID when needed */ srid = epsgToOracle(srid); geom->indicator->sdo_srid = (srid == 0) ? OCI_IND_NULL : OCI_IND_NOTNULL; if (geom->indicator->sdo_srid == OCI_IND_NOTNULL) uintToNumber(session->envp->errhp, &srid, &(geom->geometry->sdo_srid)); gtype = (((ub1)data[0]) & 0x01 ) ? 3000 : 2000; /* 3d/2d */ if (data[0] & 0x02) { /* * What PostGIS sees as a two-dimensional geometry with measure * is a three-dimensional geometry in Oracle, and the measure * dimension is the third dimension. */ gtype += 1300; if (gtype >= 4000) oracleError(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: measure dimension only supported for two-dimensional geometries"); } if (data[0] & 0x08) oracleError(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: geodetic not supported"); if (((ub1)data[0]) & 0x04) /* has bbox, offsets */ { data += 1 + 2*(gtype / 1000)*sizeof(float); } else { data += 1; } geom->indicator->sdo_gtype = OCI_IND_NOTNULL; uintToNumber(session->envp->errhp, >ype, &(geom->geometry->sdo_gtype)); return data; } unsigned ewkbPointLen(oracleSession *session, ora_geometry *geom) { return 2*sizeof(unsigned) + sizeof(double)*sdoDimension(session, geom); } char * ewkbPointFill(oracleSession *session, ora_geometry *geom, char *dest) { unsigned dim = sdoDimension(session, geom); dest = unsignedFill(POINTTYPE, dest); dest = unsignedFill(1, dest); if (geom->indicator->sdo_point._atomic != OCI_IND_NOTNULL) { /* the point must be stored in SDO_ORDINATES */ unsigned offset, etype, interpretation; double *doubleDest = (double *)dest; if (geom->indicator->sdo_elem_info != OCI_IND_NOTNULL) oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: SDO_POINT and SDO_ELEM_INFO cannot both be NULL for a point"); if (geom->indicator->sdo_ordinates != OCI_IND_NOTNULL) oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: SDO_POINT and SDO_ORDINATES cannot both be NULL for a point"); if (geom->num_elems < 3) oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: not enough values in SDO_ELEM_INFO"); offset = geom->elem[0]; etype = geom->elem[1]; interpretation = geom->elem[2]; if (etype != 1) oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: point cannot have ETYPE different from 1"); if (interpretation != 1) oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: point with SDO_INTERPRETATION different from 1 is not supported"); *doubleDest = geom->coord[offset - 1]; *(++doubleDest) = geom->coord[offset]; dest += 2 * sizeof(double); if (dim == 3) { *(++doubleDest) = geom->coord[offset + 1]; dest += sizeof(double); } } else { /* the point must be stored in SDO_POINT */ if (geom->indicator->sdo_point.x != OCI_IND_NOTNULL || geom->indicator->sdo_point.y != OCI_IND_NOTNULL || (dim == 3 && geom->indicator->sdo_point.z != OCI_IND_NOTNULL)) { oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: null point coordinates not supported"); } numberToDouble(session->envp->errhp, &(geom->geometry->sdo_point.x), dest); dest += sizeof(double); numberToDouble(session->envp->errhp, &(geom->geometry->sdo_point.y), dest); dest += sizeof(double); if (3 == dim) { numberToDouble(session->envp->errhp, &(geom->geometry->sdo_point.z), dest); dest += sizeof(double); } } return dest; } const char * setPoint(oracleSession *session, ora_geometry *geom, const char *data) { if (*((unsigned *)data) != POINTTYPE) oracleError_i(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: expected point, got type %u", *((unsigned *)data)); data += sizeof(unsigned); if (*((unsigned *)data ) != 1) oracleError(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: empty point is not supported"); data += sizeof(unsigned); geom->indicator->sdo_point._atomic = OCI_IND_NOTNULL; geom->indicator->sdo_point.x = OCI_IND_NOTNULL; doubleToNumber(session->envp->errhp, data, &(geom->geometry->sdo_point.x)); data += sizeof(double); geom->indicator->sdo_point.y = OCI_IND_NOTNULL; doubleToNumber(session->envp->errhp, data, &(geom->geometry->sdo_point.y)); data += sizeof(double); if (3 == sdoDimension(session, geom)) { geom->indicator->sdo_point.z = OCI_IND_NOTNULL; doubleToNumber(session->envp->errhp, data, &(geom->geometry->sdo_point.z)); data += sizeof(double); } return data; } unsigned ewkbLineLen(oracleSession *session, ora_geometry *geom) { return 2 * sizeof(unsigned) + sizeof(double) * geom->num_coords; } char * ewkbLineFill(oracleSession *session, ora_geometry *geom, char * dest) { unsigned i; const unsigned numC = geom->num_coords; const unsigned numPoints = numC / sdoDimension(session, geom); dest = unsignedFill(LINETYPE, dest); dest = unsignedFill(numPoints, dest); for (i=0; icoord[i], dest); return dest; } const char * setLine(oracleSession *session, ora_geometry *geom, const char *data) { unsigned i, n; if (*((unsigned *)data) != LINETYPE) oracleError_i(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: expected line, got type %u", *((unsigned *)data)); data += sizeof(unsigned); n = *((unsigned *)data) * sdoDimension(session, geom); data += sizeof(unsigned); if (!n) oracleError(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: empty line is not supported"); appendElemInfo(session, geom, numCoord(session, geom) + 1); /* start index + 1 */ appendElemInfo(session, geom, 2); /* SDO_ETYPE linestring */ appendElemInfo(session, geom, 1); /* SDO_INTERPRETATION straight line segments */ for (i=0; inum_elems / 3; /* there is the number of rings, and, for each ring the number of points * numRings%2 is there for padding */ return (numRings + 2 + numRings % 2) * sizeof(unsigned) + sizeof(double) * geom->num_coords; } const char * setPolygon(oracleSession *session, ora_geometry *geom, const char *data) { unsigned r, i, numRings; const unsigned dimension = sdoDimension(session, geom); const char * ringSizeData; if (*((unsigned *)data) != POLYGONTYPE) oracleError_i(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: expected polygon, got type %u", *((unsigned *)data)); data += sizeof(unsigned); numRings = *((unsigned *)data); data += sizeof(unsigned); if (!numRings) oracleError(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: empty polygon is not supported"); ringSizeData = data; data += (numRings+numRings%2)*sizeof(unsigned); for (r=0; rnum_elems / 3; const unsigned numC = geom->num_coords; unsigned i; dest = unsignedFill(POLYGONTYPE, dest); dest = unsignedFill(numRings, dest); for (i=0; ielem[i*3] - 1; const unsigned coord_e = i+1 == numRings ? numC : geom->elem[(i+1)*3] - 1; const unsigned numPoints = (coord_e - coord_b) / dimension; dest = unsignedFill(numPoints, dest); } /* padding */ if ( numRings % 2 != 0 ) dest = unsignedFill(0, dest); for (i=0; icoord[i], dest); return dest; } unsigned ewkbMultiPointLen(oracleSession *session, ora_geometry *geom) { const unsigned numC = geom->num_coords; const unsigned numPoints = numC / sdoDimension(session, geom); return 2*sizeof(unsigned) + (2*sizeof(unsigned)*numPoints) + sizeof(double)*numC; } char * ewkbMultiPointFill(oracleSession *session, ora_geometry *geom, char * dest) { unsigned i; const unsigned dim = sdoDimension(session, geom); const unsigned numPoints = geom->num_coords / dim; dest = unsignedFill(MULTIPOINTTYPE, dest); dest = unsignedFill(numPoints, dest); for (i=0; icoord[i*dim+j], dest); } return dest; } const char * setMultiPoint(oracleSession *session, ora_geometry *geom, const char *data) { unsigned i, j, numPoints; const unsigned dimension = sdoDimension(session, geom); if (*((unsigned *)data) != MULTIPOINTTYPE) oracleError_i(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: expected multipoint, got type %u", *((unsigned *)data)); data += sizeof(unsigned); numPoints = *((unsigned *)data); data += sizeof(unsigned); if (!numPoints) oracleError(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: empty multipoint is not supported"); for (i=0; inum_elems / 3; return 2 * sizeof(unsigned) + 2 * sizeof(unsigned) * numLines + sizeof(double) * geom->num_coords; } char * ewkbMultiLineFill(oracleSession *session, ora_geometry *geom, char * dest) { unsigned i; const unsigned numC = geom->num_coords; const unsigned dimension = sdoDimension(session, geom); const unsigned numLines = geom->num_elems / 3; dest = unsignedFill(MULTILINETYPE, dest); dest = unsignedFill(numLines, dest); for (i=0; ielem[i*3] - 1; const unsigned coord_e = i+1 == numLines ? numC : geom->elem[(i+1)*3] - 1; const unsigned numPoints = (coord_e - coord_b) / dimension; dest = unsignedFill(LINETYPE, dest); dest = unsignedFill(numPoints, dest); for (j=coord_b; jcoord[j], dest); } return dest; } const char * setMultiLine(oracleSession *session, ora_geometry *geom, const char *data) { unsigned r, numLines; if (*((unsigned *)data) != MULTILINETYPE) oracleError_i(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: expected multiline, got type %u", *((unsigned *)data)); data += sizeof(unsigned); numLines = *((unsigned *)data); data += sizeof(unsigned); if (!numLines) oracleError(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: empty multiline is not supported"); for (r=0; rnum_elems / 3; unsigned numPolygon = 0; unsigned i, j; unsigned padding = 0; for (i = 0; ielem[i*3+1] == 1003 ; for (i=0, j=0; i < numPolygon; i++) { unsigned numRings = 1; /* move j to the next ext ring, or the end */ for (j++; j < totalNumRings && geom->elem[j*3+1] != 1003; j++, numRings++); padding += numRings%2; } /* there is the type and the number of polygons, for each polygon the type * and number of rings and * for each ring the number of points and the padding */ return 2*sizeof(unsigned) + + numPolygon*(2*sizeof(unsigned)) + (totalNumRings + padding)*(sizeof(unsigned)) + sizeof(double) * geom->num_coords; } char * ewkbMultiPolygonFill(oracleSession *session, ora_geometry *geom, char * dest) { const unsigned dimension = sdoDimension(session, geom); const unsigned numC = geom->num_coords; const unsigned totalNumRings = geom->num_elems / 3; unsigned numPolygon = 0; unsigned i, j; for (i = 0; ielem[i*3+1] == 1003 ; dest = unsignedFill(MULTIPOLYGONTYPE, dest); dest = unsignedFill(numPolygon, dest); for (i=0, j=0; i < numPolygon; i++) { unsigned end, k; unsigned numRings = 1; /* move j to the next ext ring, or the end */ for (j++; j < totalNumRings && geom->elem[j*3+1] != 1003; j++, numRings++); dest = unsignedFill(POLYGONTYPE, dest); dest = unsignedFill(numRings, dest); /* * Reset j to be on the exterior ring of the current polygon * and output rings number of points. */ for (end = j, j -= numRings; jelem[j*3] - 1; const unsigned coord_e = j+1 == totalNumRings ? numC : geom->elem[(j+1)*3] - 1; const unsigned numPoints = (coord_e - coord_b) / dimension; dest = unsignedFill(numPoints, dest); } if (numRings%2) dest = unsignedFill(0, dest); /* padding */ for (end = j, j -= numRings; jelem[j*3] - 1; const unsigned coord_e = j+1 == totalNumRings ? numC : geom->elem[(j+1)*3] - 1; for (k=coord_b; kcoord[k], dest); } } return dest; } const char * setMultiPolygon(oracleSession *session, ora_geometry *geom, const char *data) { unsigned p, numPolygons; if (*((unsigned *)data) != MULTIPOLYGONTYPE) oracleError_i(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: expected multipolygon, got type %u", *((unsigned *)data)); data += sizeof(unsigned); numPolygons = *((unsigned *)data); data += sizeof(unsigned); if (!numPolygons) oracleError(FDW_ERROR, "error converting geometry to SDO_GEOMETRY: empty multipolygon is not supported"); for (p=0; penvp->envhp, session->envp->errhp, (OCIColl *)(geom->geometry->sdo_ordinates), &n), session->envp->errhp) != OCI_SUCCESS) oracleError_d(FDW_ERROR, "cannot get size of ordinate collection", oraMessage); return n; } /* * coord * Get the i'th element of the SDO_ORDINATES collection of "geom". * This should only be called from unpack(). */ double coord(oracleSession *session, ora_geometry *geom, unsigned i) { double coord; boolean exists; OCINumber *oci_number; OCIInd *indicator; if (checkerr( OCICollGetElem(session->envp->envhp, session->envp->errhp, (OCIColl *)(geom->geometry->sdo_ordinates), (sb4)i, &exists, (dvoid **)&oci_number, (dvoid **)&indicator), session->envp->errhp) != OCI_SUCCESS) oracleError_d(FDW_ERROR, "error fetching element from ordinate collection", oraMessage); if (! exists) oracleError_i(FDW_ERROR, "element %u of ordinate collection does not exist", i); if (*indicator == OCI_IND_NULL) oracleError_i(FDW_ERROR, "element %u of ordinate collection is NULL", i); /* convert the element to double */ numberToDouble(session->envp->errhp, oci_number, &coord); return coord; } /* * numElemInfo * Get the number of elements in the SDO_ELEM_INFO collection of "geom". * This should only be called from unpack() and the set...() functions. */ unsigned numElemInfo(oracleSession *session, ora_geometry *geom) { int n; if (checkerr( OCICollSize (session->envp->envhp, session->envp->errhp, (OCIColl *)(geom->geometry->sdo_elem_info), &n), session->envp->errhp) != OCI_SUCCESS) oracleError_d(FDW_ERROR, "cannot get size of element info collection", oraMessage); return n; } /* * elemInfo * Get the i'th element of the SDO_ELEM_INFO collection of "geom". * This should only be called from unpack(). */ unsigned elemInfo(oracleSession *session, ora_geometry *geom, unsigned i) { unsigned info; boolean exists; OCINumber *oci_number; OCIInd *indicator; if (checkerr( OCICollGetElem(session->envp->envhp, session->envp->errhp, (OCIColl *)(geom->geometry->sdo_elem_info), (sb4)i, &exists, (dvoid **)&oci_number, (dvoid **)&indicator), session->envp->errhp) != OCI_SUCCESS) oracleError_d(FDW_ERROR, "error fetching element from element info collection", oraMessage); if (! exists) oracleError_i(FDW_ERROR, "element %u of element info collection does not exist", i); if (*indicator == OCI_IND_NULL) oracleError_i(FDW_ERROR, "element %u of element info collection is NULL", i); numberToUint(session->envp->errhp, oci_number, &info); return info; } /* * unpack * Unpack the data from the collections and store them in "geom". * Also remove all the elements with SDO_ETYPE 0. */ void unpack(oracleSession *session, ora_geometry *geom) { int elemCount, coordCount, elem_i, coord_i, elem_pos = 0, coord_pos = 0; unsigned next_offset = 0; /* don't do anything for NULL SDO_ELEM_INFO and SDO_ORDINATES */ if (geom->indicator->sdo_elem_info != OCI_IND_NOTNULL && geom->indicator->sdo_ordinates != OCI_IND_NOTNULL) return; /* both SDO_ELEM_INFO and SDO_ORDINATES must be NOT NULL */ if (geom->indicator->sdo_elem_info != OCI_IND_NOTNULL || geom->indicator->sdo_ordinates != OCI_IND_NOTNULL) oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: SDO_ELEM_INFO and SDO_ORDINATES must either both be NULL"); elemCount = numElemInfo(session,geom); coordCount = numCoord(session,geom); if (elemCount%3 != 0) oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: size of SDO_ELEM_INFO must be a multiple of three"); /* this might be too big if some elements with etype 0 are deleted */ geom->elem = oracleAlloc(elemCount * sizeof(unsigned)); geom->coord = oracleAlloc(coordCount * sizeof(double)); for (elem_i = 0; elem_i + 1 < elemCount; elem_i +=3) { unsigned offset, etype, interpretation; if (next_offset == 0) offset = elemInfo(session, geom, elem_i); else offset = next_offset; etype = elemInfo(session, geom, elem_i + 1); interpretation = elemInfo(session, geom, elem_i + 2); if (elem_i + 4 < elemCount) next_offset = elemInfo(session, geom, elem_i + 3); else next_offset = coordCount + 1; if (etype != 0) { /* * Copy the three ELEM_INFO entries and their ordinates. */ /* adjust offset */ geom->elem[elem_pos] = coord_pos + 1; geom->elem[elem_pos + 1] = etype; geom->elem[elem_pos + 2] = interpretation; elem_pos += 3; /* copy ordinates for this entry */ for (coord_i=offset - 1; coord_i < next_offset - 1; ++coord_i) geom->coord[coord_pos++] = coord(session, geom, coord_i); } } geom->num_elems = elem_pos; geom->num_coords = coord_pos; if (elem_pos == 0) oracleError(FDW_ERROR, "error converting SDO_GEOMETRY to geometry: SDO_ELEM_INFO must contain element with non-zero ETYPE"); } /* * freeUnpacked * Free the memory allocated in "unpack" and clear the settings in "geom". */ void freeUnpacked(oracleSession *session, ora_geometry *geom) { if (geom->elem != NULL) oracleFree(geom->elem); if (geom->coord != NULL) oracleFree(geom->coord); geom->num_elems = -1; geom->elem = NULL; geom->num_coords = -1; geom->coord = NULL; } /* * checkerr * Call OCIErrorGet to get error message and error code. */ sword checkerr(sword status, OCIError *handle) { unsigned length; oraMessage[0] = '\0'; if (status == OCI_SUCCESS_WITH_INFO || status == OCI_ERROR) { OCIErrorGet(handle, (ub4)1, NULL, &err_code, (text *)oraMessage, (ub4)ERRBUFSIZE, OCI_HTYPE_ERROR); length = strlen(oraMessage); if (length > 0 && oraMessage[length-1] == '\n') oraMessage[length-1] = '\0'; } if (status == OCI_SUCCESS_WITH_INFO) status = OCI_SUCCESS; if (status == OCI_NO_DATA) { strcpy(oraMessage, "ORA-00100: no data found"); err_code = (sb4)100; } return status; }