GridDB Community Edition 4.1以降のバージョンは、開発者がアプリケーションで時系列分析と地理空間分析の両方を組み合わせることができるGeometryデータタイプを備えています。 以前に GridDBでのジオメトリ値の使用と、ジオメトリデータを活用したアプリケーションのブログで、GridDBのジオメトリ機能について説明しました。
このブログでは、ニューヨーク市の過去の犯罪の通報データを用いて、Geometryデータタイプの実用的な使い方を説明します。 このデータで提供される犯罪レポートの緯度と経度を使って、GridDBで通報が発生したエリアを特定します。ニューヨーク市のオープンデータから犯罪データを取り込む方法を示した後、セントラルパークにおける犯罪件数が月ごとにどのように変わるかを調べます。また、個々の地区の通報件数を確認して、外部ポリゴンデータを読み込みます。
空間データポイントでGridDBのGeometryデータタイプを使用する主な目的は、ポイント、ポリライン(パス)、またはポリゴン(エリア)が交差する場所を検索できるようにすることです。 ポイント、ポリライン、およびポリゴンは、地図上のベクトルジオメトリオブジェクトを定義するマークアップ言語であるWell-known-text (WKT)を使用して定義されます。
データ取り込み
過去の犯罪データはニューヨーク市オープンデータから取得しました。 次の表は、CSVデータフィールドとGridDBスキーマの両方を示しています。
| CSV Values | GridDB Schema | 
|---|---|
| 
 | public class Complaint {
    int CMPLNT_NUM;
    Date CMPLNT_FR_DT;
    Date CMPLNT_TO_DT;
    int ADDR_PCT_CD;
    Date RPT_DT;
    int KY_CD;
    String OFNS_DESC;
    int PD_CD;
    String PD_DESC;
    String CRM_ATPT_CPTD_CD;
    String LAW_CAT_CD;
    String BORO_NM;
    String LOC_OF_OCCUR_DESC;
    String PREM_TYP_DESC;
    String JURIS_DESC;
    int JURISDICTION_CODE;
    String PARKS_NM;
    String HADEVELOPT;
    String HOUSING_PSA;
    int X_COORD_CD;
    int Y_COORD_CD;
    String SUSP_AGE_GROUP;
    String SUSP_RACE;
    String SUSP_SEX;
    int TRANSIT_DISTRICT;
    float Latitude;
    float Longitude;
    Geometry Lat_Lon;
    String PATROL_BORO;
    String STATION_NAME;
    String VIC_AGE_GROUP;
    String VIC_RACE;
    String VIC_SEX;
} | 
簡単にするために、データを複数のコンテナーに分割するのではなく、1つのコンテナーのみを使用しています。
CSVParserライブラリを使用してCSVを簡単に解析します。
   Iterable records = CSVFormat.RFC4180.withFirstRecordAsHeader().parse(in);
   for (CSVRecord record : records) {
        Complaint c = parseCsvRecord(record);
        if(c != null)
            col.put(c);
   }
   col.commit();
parseCsvRecord関数内でいくつかデータの変更をします。 まず、通報の時間の形式は標準設定されていませんが、簡単に解析できるようMM/DD/YYYYおよびHH:MM:SSの形式に変更します。
String dt[] = r.get("CMPLNT_FR_DT").split("/");
String tm[] = r.get("CMPLNT_FR_TM").split(":");
c.CMPLNT_FR_DT = new Date(Integer.parseInt(dt[2])-1900, Integer.parseInt(dt[0])-1, Integer.parseInt(dt[1]), Integer.parseInt(tm[0]), Integer.parseInt(tm[1]), Integer.parseInt(tm[2]));
未加工のCSVには、「緯度経度」で犯罪が発生したポイントのWKTテキストが含まれていますが、受け入れられるWKT形式はPOINT(x y)であり、緯度はY軸を示し、経度はX軸なので反転します。
c.Lat_Lon =   Geometry.valueOf("POINT("+c.Longitude+" "+c.Latitude+")");
各管区内の犯罪件数
ニューヨーク市のオープンデータは、こちらから入手可能な個々の警察管区のWKTポリゴンも提供しています。 通報データと同様に、CSVParserを使用して簡単にロードできますが、各地区は複数のポリゴンで構成され、WKT MULTIPOLYGONタイプを使用しているため、MULTIPOLYGONを単純なPOLYGONに分割するにはさらに処理が必要です。
String polys[] = record.get("the_geom").split("\\),");
int count=0;
for(int i=0; i < polys.length; i++) {
    String subpoly = polys[i].replace("MULTIPOLYGON (", "").replace(")))", ")");
    query = col.query("select * where ST_MBRIntersects(Lat_Lon, ST_GeomFromText('POLYGON"+subpoly+")') )");
    rs = query.fetch(false);
    count =+ rs.size();
}
結果は次のとおりです。
第1地区 : 243 第52地区 : 888 第5地区 : 177 第60地区 : 185 第6地区 : 216 第61地区 : n/a 第71地区 : 227 第62地区 : 210 第72地区 : 262 第63地区 : 324 第7地区 : 132 第66地区 : 261 第9地区 : 233 第68地区 : 233 第22地区 : 345 第69地区 : 220 第10地区 : 203 第70地区 : 369 第13地区 : 400 第76地区 : 135 第14地区 : 428 第77地区 : 334 第17地区 : 174 第78地区 : 211 第20地区 : 132 第81地区 : 12 第18地区 : 379 第83地区 : 498 第19地区 : 225 第84地区 : 175 第23地区 : 225 第88地区 : 174 第24地区 : 147 第90地区 : 290 第25地区 : 336 第94地区 : 102 第79地区 : 266 第100地区 : 8 第26地区 : 217 第101地区 : 0 第28地区 : 213 第102地区 : 283 第30地区 : 206 第103地区 : 367 第32地区 : 361 第104地区 : 511 第73地区 : 410 第105地区 : 481 第33地区 : 152 第106地区 : 227 第34地区 : 224 第107地区 : 294 第75地区 : 529 第108地区 : 262 第40地区 : 444 第109地区 : 299 第41地区 : 304 第110地区 : 431 第42地区 : 487 第111地区 : 138 第43地区 : 408 第112地区 : 178 第48地区 : 495 第113地区 : n/a 第44地区 : 559 第114地区 : 28 第45地区 : 323 第115地区 : 246 第46地区 : 400 第120地区 : 228 第47地区 : 441 第121地区 : 194 第49地区 : 267 第122地区 : 217 第50地区 : 219 第123地区 : 83 第67地区 : 504
セントラルパーク
まず最初の地理空間分析として、セントラルパークでの犯罪件数を月ごとに調べ、犯罪の通報件数が気温に応じて増減するかどうかを確認します。 セントラルパークは南北軸に位置合わせされていないため、基本的な地理空間分析クエリのように境界ボックス(where lat > min && lat < max && lon > min && lon < max) を単純に使用することはできません。
代わりに、セントラルパークの各コーナーのポリゴンを作成し、そのポリゴンと交差する犯罪の通報件数を照会します。
セントラルパークのポイントを見つけてWKTオブジェクトを構築するには、Wicketを使うと便利です。

これで、TQLクエリを作成できるようになりました。
String CentralParkWKT = "POLYGON((-73.97308900174315 40.764422448981996,-73.98192956265623 40.76812781417226,-73.9584064734938 40.80087951931638,-73.94982340464614 40.797240957024385,-73.97308900174315 40.764422448981996))";
for(int month=0; month <= 11; month++) {
    int count=0;
    for (int year=108; year <= 118; year++) {
        Date start = new Date(year, month, 1);
        Date end = new Date(year, month+1, 1);
	Query query = col.query("select * where ST_MBRIntersects(Lat_Lon, ST_GeomFromText('"+CentralParkWKT+"')) and CMPLNT_FR_DT >= TO_TIMESTAMP_MS("+start.getTime()+")  and CMPLNT_FR_DT < TO_TIMESTAMP_MS("+ end.getTime()+") ");
        RowSet rs = query.fetch(false);
        count += rs.size(); 
    }
    System.out.println(month+": "+count);
}
では、先ほどの質問、気温が変化に伴い犯罪の件数は変化するか、の答えはどうだったでしょうか?
1月: 30 2月: 23 3月: 36 4月: 33 5月: 29 6月: 19 7月: 26 8月: 49 9月: 25 10月: 23 11月: 26 12月: 18
12月と8月の数字はこの仮説を支持しますが、1月と6月の数字は支持しないため、この仮説は事実ではないことが分かりました。
データをより詳しく見たり、完全なコードを確認したりする場合は、こちらからデータを入手することができます。
ブログの内容について疑問や質問がある場合は Q&A サイトである Stack Overflow に質問を投稿しましょう。 GridDB 開発者やエンジニアから速やかな回答が得られるようにするためにも "griddb" タグをつけることをお忘れなく。 https://stackoverflow.com/questions/ask?tags=griddb
