MongoDBの位置情報にJavaライブラリのMorphia（モルフィア）で取得する

# 前提条件:
* システムに MongoDB 4.4 以上がインストールされていること。
* Java 11 が利用できる環境であること。

実際のアプリケーションでは、近くのレストランを検索したり、2つの場所間の距離を検索したりするなど、地理空間 (位置ベースとも呼ばれます) クエリを実装する必要があることがよくあります。

MongoDB は、オブジェクト型 geoJSON の下に位置を格納する機能を提供し、[経度、緯度] 形式の座標フィールドに対するそれらの座標を格納します。ここで、経度は [-180, 180] の間、緯度は [-90,90] の間にある必要があります。

実装に進む前に、MongoDB によって提供される geoJSON タイプについて説明しましょう。

## 1. Point: 
以下のように宣言された単一の位置点を表します。
```json
{ type: "Point", coordinates: [ 27, 67 ] }
```

## 2. LineString: 
LineString 座標フィールドは、現実世界のパスを表す2つ以上のポイントの配列です。
それは次のように宣言されています。
```json
{ type: "LineString", coordinates: [ [ 27, 67 ], [ 52, 4 ] ] }
```

## 3. Polygon: 
Polygon 座標フィールドは、線形リングの配列です。線形リングには少なくとも 4 つの座標があり、最初の座標は最後の座標と同じで、ループを形成します。線形リングの詳細については、こちらをご覧ください。

ポリゴン座標には、単一の線形リングまたは複数のリングが含まれる場合があります。
単一のリングの場合、ポリゴンは次のように宣言できます。
```json
{
  type: "Polygon",
  coordinates: [[[2, 1], [3, 4], [5, 1], [2, 1]]]
}
```

## 4. MultiPoint:
MultiPoint 座標では、フィールドは点の配列です。次のように定義されます。
```json
{
  type: "MultiPoint",
  coordinates: [
      [162.07985, 20.66911],
      [126.73567, -21.59424],
      [-57.94873, 0.03998]
   ]
} 
```

## 5. MultiLineString: 
MultiLineString では、座標フィールドは折れ線の配列です。
```json
{
   type: "MultiLineString",
   coordinates: [
      [[162.07985, 20.66911], [163.07945, 21.56721]],
      [[126.73567, -21.59424], [127.74537, -22.54623]],
      [[-57.94873, 0.03998], [-58.73823, 1.12934]]
   ]
}
```

## 6. MultiPolygon: 
MultiPolygon では、座標フィールドはポリゴンの配列です。
```json
{
  type: "MultiPolygon",
  coordinates: [
      [[[0.01, 0.02], [7.01, 1.03], [3.01, 5.1], [0.01, 0.02]]],
      [[[2.2, 1.01], [4.02, 2.01], [3.2, 3], [2.2, 1.01]]]
   ]
}
```

## 7. GeometryCollection: 
これは、上記のジオメトリを 1 つの変数に格納するために使用される複雑な geoJSON オブジェクトです。次のように定義されます。
```json
{
  type: "GeometryCollection",
  geometries: [
    {
      type: "MultiPoint",
      coordinates: [
          [162.07985, 20.66911],
          [126.73567, -21.59424],
          [-57.94873, 0.03998]
       ]
     },
    {
      type: "MultiLineString",
      coordinates: [
          [[162.07985, 20.66911], [163.07945, 21.56721]],
          [[126.73567, -21.59424], [127.74537, -22.54623]],
          [[-57.94873, 0.03998], [-58.73823, 1.12934]]
       ]
     },

    {
      type: "MultiPolygon",
      coordinates: [
          [[[0.01, 0.02], [7.01, 1.03], [3.01, 5.1], [0.01, 0.02]]],
          [[[2.0, 1.01], [4.0, 2.0], [3.02, 3.0], [2.0, 1.01]]]
       ]
     }
   ]
}
```

# 地理空間インデックス
MongoDB では、テキスト検索の場合と同様に、地理空間クエリを実行するために地理空間インデックスを作成する必要があります。
地理空間検索用のインデックスには 2 種類あります。

## 1. 2d:
2D インデックスは、2 次元の幾何学的平面で計算を実行するときに使用されます。これらについては、この記事では詳しく説明しません。2D インデックスは、実行することで作成できます
```
db.collection.createIndex( { <location field> : "2d" } )
```

location_fieldの項目で扱われる座標の値は [lng,lat] で登録する必要があります。

## 2. 2dsphere: 
2dsphere インデックスは、地球のような球体でクエリ/計算を実行するときに使用されます。
これは、次のように作成できます。ここで、場所フィールドは、タイプ geoJSON オブジェクトまたは従来の座標にすることができます。
```
db.collection.createIndex( { <location_field> : "2dsphere" } )
```

では、実装してみましょう。

# Java で MongoDB を使う
Java で MongoDB を使うにあたり、[Morphia](https://www.mongodb.com/languages/morphia) というライブラリを利用します。

https://www.mongodb.com/languages/morphia

Morphia は、MongoDB 用の Java ドライバーのラッパーです。MongoDB ドキュメントの ODM (オブジェクト ドキュメント モデル) として機能します。その当初の目標は、POJO (Plain Old Java Objects) への簡単なマッピングを提供することでした。現在、新しいバージョンの Java ドライバーは、このマッピングをそのままサポートしています。また、Morphia には、参照や注釈ベースのインデックス作成など、追加の価値を提供できる機能がまだいくつかあります。

今回は geoJSON で必要な部分しか書きませんが使い方は以下のドキュメントに載っているので、参考にしてください。

https://morphia.dev/landing/index.html

利用を始める前に maven で以下を追加します。

```xml:pom.xml
<dependencies>
    <dependency>
        <groupId>dev.morphia.morphia</groupId>
        <artifactId>morphia-core</artifactId>
        <version>2.3.0</version>
    </dependency>
</dependencies>
```

こちらは Point データを 2dsphere インデックスのものを検索する方法を参考に載せています。

```java
// データストアを生成する
final Datastore datastore = Morphia.createDatastore(MongoClients.create(), "morphia_example");

// エンティティクラスの場所を　Morphia　に設定します
// パッケージの場所またはクラスを複数指定で呼び出すことができます
datastore.getMapper().mapPackage("dev.morphia.example");

// インデックスを作成する
datastore.ensureIndexes();

// locationの値に対して指定の緯度経度から最小・最大範囲を指定して検索する。
final var point = new Point(new Position(lng, lat)); // 緯度経度の指定
final var nearFilter = Filters.near("location", point);
final Map <String, Double> opts = new HashMap <>();
opts.put("$maxDistance", 2000); // 最大範囲の指定（メートル指定）
opts.put("$minDistance", 1000); // 最小範囲の指定（メートル指定）
nearFilter.applyOpts(opts);

// 結果をリスト形式で受け取る
final var tourspots = ds.find(Tourspot.class)
        .filter(nearFilter)
        .iterator().toList();
```

ここら辺のサンプルソースがなくて自力で調べて実装したので、備忘録として記事にしてみました。
ほかに Morphia の参考になるものがあればお知らせください。

昔の記事はあるのですが、最新版の参考文献がほぼ無いのですよね。

### 参考サイト

https://www.topcoder.com/thrive/articles/geospatial-location-queries-in-mongodb