GeoJsonデータの検索

Oracle NoSQL Databaseには、検索ジオメトリと特定の関係を持つGeoJsonデータを検索する4つの関数が用意されています。

boolean geo_intersect(any*, any*)

オペランドが単一の有効なジオメトリ・オブジェクトを返さないことがコンパイル時に検出されると、エラーが発生します。それ以外の場合、実行時の動作は次のようになります。
  • いずれかのオペランドが0個または複数の項目を返した場合はfalseを返します。
  • いずれかのオペランドがNULLを返した場合はNULLを返します。
  • いずれかのオペランドが有効なジオメトリ・オブジェクトではない項目を返した場合は、falseを返します。
最後に、両方のオペランドが単一のジオメトリ・オブジェクトを返した場合、2つのジオメトリに共通点があればtrueを返し、そうでない場合はfalseを返します。

boolean geo_inside(any*, any*)

オペランドが単一の有効なジオメトリ・オブジェクトを返さないことがコンパイル時に検出されると、エラーが発生します。それ以外の場合、実行時の動作は次のようになります。
  • いずれかのオペランドが0個または複数の項目を返した場合はfalseを返します。
  • いずれかのオペランドがNULLを返した場合はNULLを返します。
  • いずれかのオペランドが有効なジオメトリ・オブジェクトではない項目を返した場合は、falseを返します(ただし、オペランドが有効なジオメトリを返さないことがコンパイル時に検出された場合は、エラーが発生します)。
  • 2番目のオペランドがポリゴンではないジオメトリ・オブジェクトを返した場合は、falseを返します。
最後に、両方のオペランドが単一のジオメトリ・オブジェクトを返し、2番目のジオメトリがポリゴンである場合、最初のジオメトリが2番目のポリゴンの内部に完全に含まれている場合、つまり、最初のジオメトリのすべてのポイントがポリゴンの内部に属している場合は、trueを返します。それ以外の場合はfalseを返します。ポリゴンの内部は、ポリゴンの境界を定義する線形リングのポイントを除いて、ポリゴン領域内のすべてのポイントです。

boolean geo_within_distance(any*, any*, double)

最初の2つのオペランドのいずれかが単一の有効なジオメトリ・オブジェクトを返さないことがコンパイル時に検出されると、エラーが発生します。それ以外の場合、実行時の動作は次のようになります。
  • 最初の2つのオペランドのいずれかが0個または複数の項目を返した場合はfalseを返します。
  • 最初の2つのオペランドのいずれかがNULLを返した場合はNULLを返します。
  • 最初の2つのオペランドのいずれかが有効なジオメトリ・オブジェクトではない項目を返した場合は、falseを返します。
最後に、最初の2つのオペランドの両方が単一のジオメトリ・オブジェクトを返した場合、最初のジオメトリが2番目のジオメトリからNメートル以内にある場合はtrueを返します。Nは3番目のオペランドによって返された数値です。それ以外の場合はfalseを返します。2つのジオメトリの間の距離は、1つ目のポイントが1つ目のジオメトリに属し、2つ目のポイントが2つ目のジオメトリに属しているポイントのペアの距離の最小値として定義されます。Nが負の数の場合は、0に設定されます。

boolean geo_near(any*, any*, double)

geo_nearは、内部でgeo_within_distanceに変換され、2つのジオメトリ間の距離に(暗黙的な) ORDER BYが指定されます。ただし、問合せに(明示的な) ORDER BYがすでに存在する場合、距離による順序付けは実行されません。geo_near関数はWHERE句にのみ使用できます。この場合、最上位レベルの述語(ORまたはNOT演算子の下にネストされていない)にする必要があります。

前述の検索関数以外に、次の2つの関数が用意されています。

double geo_distance(any*, any*)

オペランドが単一の有効なジオメトリ・オブジェクトを返さないことがコンパイル時に検出されると、エラーが発生します。それ以外の場合、実行時の動作は次のようになります。
  • いずれかのオペランドが0個または複数の項目を返した場合は-1を返します。
  • いずれかのオペランドがNULLを返した場合はNULLを返します。
  • オペランドのいずれかがジオメトリでない場合は-1を返します。
それ以外の場合は、2つの入力ジオメトリ間の測地距離が返されます。返される距離は、1つ目のポイントが1つ目のジオメトリに属し、2つ目のポイントが2つ目のジオメトリに属しているポイントのペアの距離の最小値として定義されます。このような2つのポイントの間の距離は、ポイントを結ぶ測地線の長さです。

boolean geo_is_geometry(any*)

  • オペランドが0個または複数の項目を返した場合はfalseを返します。
  • オペランドがNULLを返した場合はNULLを返します。
  • 入力が単一の有効なジオメトリ・オブジェクトである場合はtrueを返します。それ以外の場合はfalseを返します。

前述の地理関数は、ジオメトリ・オブジェクトに対しては機能しますが、FeatureやFeatureCollectionには機能しません。ただし、含まれているジオメトリ・オブジェクトを地理関数に渡すことによって、FeatureやFeatureCollectionに対する問合せを効果的に行うことはできます。この例を次の項に示します。

例10-1 GeoJsonデータの検索

行に該当するものが格納されている表を考えてみます。この表には、主キーとしてのid列とjsonタイプのpoi列があります。

CREATE TABLE PointsOfInterest (
    id INTEGER, poi JSON, 
PRIMARY KEY(id));

INSERT INTO PointsOfInterest VALUES (
    1,
    {
        "kind" : "city hall",
        "address" : { 
            "state" : "CA",
            "city" : "Campbell",
            "street" : "70 North 1st street"
        },
        "location" : {
            "type" : "point", 
            "coordinates" : [121.94,37.29] 
        }
    }
);
INSERT INTO PointsOfInterest VALUES (
    2,
    {
        "kind" : "nature park",
        "name" : "castle rock state park",
        "address" : { 
            "state" : "CA",
            "city" : "Los Gatos",
            "street" : "15000 Skyline Blvd"
        },
        "location" : { 
            "type" : "polygon",
            "coordinates" : [
                [
                    [122.1301, 37.2330],
                    [122.1136, 37.2256],
                    [122.0920, 37.2291],
                    [122.1020, 37.2347],
                    [122.1217, 37.2380],
                    [122.1301, 37.2330]
                ]
            ]
        }
    }
);

次の問合せでは、北カリフォルニアの自然公園を検索します。この問合せでは、geo_insideではなくgeo_intersectを使用して、隣接する州との境界線をまたぐ公園を含めます。

SELECT t.poi AS park
FROM PointsOfInterest t
WHERE t.poi.kind = "nature park" 
AND
geo_intersect(
    t.poi.location,
    { 
        "type" : "polygon",
        "coordinates" : [
            [
                [121.94, 36.28],
                [117.52, 37.38],
                [119.99, 39.00],
                [120.00, 41.97],
                [124.21, 41.97],
                [124.39, 40.42],
                [121.94, 36.28]
            ]
        ]
    }
);

次の問合せでは、指定されたルートの1マイル以内にあるガソリン・スタンドを検索します。返されるガソリン・スタンドは、ルートからの距離ごとに昇順で並べられます。

SELECT 
t.poi AS gas_station,
geo_distance(
    t.poi.location,
    { 
        "type" : "LineString",
        "coordinates" : [
            [121.9447, 37.2975],
            [121.9500, 37.3171],
            [121.9892, 37.3182],
            [122.1554, 37.3882],
            [122.2899, 37.4589],
            [122.4273, 37.6032],
            [122.4304, 37.6267],
            [122.3975, 37.6144]
        ]
    }
) AS distance
FROM PointsOfInterest t
WHERE t.poi.kind = "gas station" 
AND
geo_near( 
    t.poi.location,
    { 
        "type" : "LineString",
        "coordinates" : [
            [121.9447, 37.2975],
            [121.9500, 37.3171],
            [121.9892, 37.3182],
            [122.1554, 37.3882],
            [122.2899, 37.4589],
            [122.4273, 37.6032],
            [122.4304, 37.6267],
            [122.3975, 37.6144]
        ]
    },
    1609
);

例10-2 GeoJsonデータの検索

この例では、Oracle NoSQL DatabaseでFeatureCollectionを問い合せる方法を示します。会社に関する情報を格納するcompanies表を考えます。この情報には、各会社のオフィスの所在地、各オフィス住所の不動産などが含まれます。

CREATE TABLE companies (
    id INTEGER, info JSON, PRIMARY KEY(id));

INSERT INTO companies VALUES (
    1,
    {
        "id" : 1,
        "info" : {
            "name" : "acme",
            "CEO" : "some random person",
            "locations" : {
                "type" : "FeatureCollection",
                "features" : [
                    {
                        "type" : "Feature",
                        "geometry" : { 
                            "type" : "point", 
                            "coordinates" : [ 23.549, 35.2908 ] 
                        },
                        "properties" : {
                            "kind" : "development",
                            "city" : "palo alto"
                        }
                    },
                    {
                        "type" : "Feature",
                        "geometry" : { 
                            "type" : "point", 
                            "coordinates" : [ 23.9, 35.17 ] 
                        },
                        "properties" : {
                            "kind" : "sales",
                            "city" : "san jose"
                        }
                    }
                ]
            }
        }
    }
);

次の問合せでは、検索地域内に販売代理店がある会社を検索して、検索された会社ごとに、同じ検索地域内の販売代理店の地理ロケーションを含む配列を返します。

SELECT id,
c.info.locations.features [
    geo_intersect( 
        $element.geometry,
        { 
            "type" : "polygon",
            "coordinates" : [ 
                [
                    [23.48, 35.16],
                    [24.30, 35.16],
                    [24.30, 35.70],
                    [23.48, 35.70],
                    [23.48, 35.16]
                ] 
            ]
        } 
    )
    AND
    $element.properties.kind = "sales"
].geometry AS loc
FROM companies c
WHERE EXISTS c.info.locations.features [
    geo_intersect(
        $element.geometry,
        { 
            "type" : "polygon",
            "coordinates" : [ 
                [
                    [23.48, 35.16],
                    [24.30, 35.16],
                    [24.30, 35.70],
                    [23.48, 35.70],
                    [23.48, 35.16]
                ] 
            ]
        } 
    )
    AND
    $element.properties.kind = "sales"
] ;

この問合せを効率的に実行するために、次の索引を作成できます。

CREATE INDEX idx_kind_loc ON companies (
info.locations.features[].properties.kind AS STRING,
info.locations.features[].geometry AS POINT);