[Groonga-commit] groonga/groonga [master] [geo] geo_in_circle() supports approximate type. refs #1226

Back to archive index

null+****@clear***** null+****@clear*****
2011年 12月 28日 (水) 15:00:14 JST


Kouhei Sutou	2011-12-28 15:00:14 +0900 (Wed, 28 Dec 2011)

  New Revision: 591f6c306292d6ab3667aea0dd9e7cb720259aa6

  Log:
    [geo] geo_in_circle() supports approximate type. refs #1226
    
    geo_in_circle() without index isn't supported yet.

  Modified files:
    include/groonga.h
    lib/geo.c
    lib/geo.h
    lib/proc.c
    test/unit/core/test-command-select-geo.c

  Modified: include/groonga.h (+0 -25)
===================================================================
--- include/groonga.h    2011-12-28 14:44:11 +0900 (acfed1b)
+++ include/groonga.h    2011-12-28 15:00:14 +0900 (ba3c6a7)
@@ -1826,31 +1826,6 @@ typedef struct {
 
 
 /**
- * grn_geo_select_in_circle:
- * @index: the index column for TokyoGeoPoint or WGS84GeoPpoint type.
- * @center_point: the center point of the target circle. (ShortText, Text,
- * LongText, TokyoGeoPoint or WGS84GeoPoint)
- * @distance: the radius of the target circle (Int32,
- * UInt32, Int64, UInt64 or Float) or the point
- * on the circumference of the target circle. (ShortText, Text, LongText,
- * TokyoGeoPoint or WGS84GeoPoint)
- * @res: the table to store found record IDs. It must be
- * GRN_TABLE_HASH_KEY type table.
- * @op: the operator for matched records.
- *
- * It selects records that are in the circle specified by
- * @center_point and @distance from @center_point. Records
- * are searched by @index. Found records are added to @res
- * table with @op operation.
- **/
-GRN_API grn_rc grn_geo_select_in_circle(grn_ctx *ctx,
-                                        grn_obj *index,
-                                        grn_obj *center_point,
-                                        grn_obj *distance,
-                                        grn_obj *res,
-                                        grn_operator op);
-
-/**
  * grn_geo_select_in_rectangle:
  * @index: the index column for TokyoGeoPoint or WGS84GeoPpoint type.
  * @top_left_point: the top left point of the target

  Modified: lib/geo.c (+128 -50)
===================================================================
--- lib/geo.c    2011-12-28 14:44:11 +0900 (f0b1e9f)
+++ lib/geo.c    2011-12-28 15:00:14 +0900 (27c96c7)
@@ -632,33 +632,87 @@ grn_geo_table_sort(grn_ctx *ctx, grn_obj *table, int offset, int limit,
 }
 
 grn_rc
+grn_geo_resolve_approximate_type(grn_ctx *ctx, grn_obj *type_name,
+                                 grn_geo_approximate_type *type)
+{
+  grn_rc rc;
+  grn_obj approximate_type;
+
+  GRN_TEXT_INIT(&approximate_type, 0);
+  rc = grn_obj_cast(ctx, type_name, &approximate_type, GRN_FALSE);
+  if (rc == GRN_SUCCESS) {
+    const char *name;
+    unsigned int size;
+    name = GRN_TEXT_VALUE(&approximate_type);
+    size = GRN_TEXT_LEN(&approximate_type);
+    if ((strncmp("rectangle", name, size) == 0) ||
+        (strncmp("rect", name, size) == 0)) {
+      *type = GRN_GEO_APPROXIMATE_RECTANGLE;
+    } else if ((strncmp("sphere", name, size) == 0) ||
+               (strncmp("sphr", name, size) == 0)) {
+      *type = GRN_GEO_APPROXIMATE_SPHERE;
+    } else if ((strncmp("ellipsoid", name, size) == 0) ||
+               (strncmp("ellip", name, size) == 0)) {
+      *type = GRN_GEO_APPROXIMATE_ELLIPSOID;
+    } else {
+      ERR(GRN_INVALID_ARGUMENT,
+          "geo distance approximate type must be one of "
+          "[rectangle, rect, sphere, sphr, ellipsoid, ellip]"
+          ": <%.*s>",
+          size, name);
+    }
+  }
+  GRN_OBJ_FIN(ctx, &approximate_type);
+
+  return rc;
+}
+
+typedef double (*grn_geo_distance_raw_func)(grn_ctx *ctx,
+                                            grn_geo_point *point1,
+                                            grn_geo_point *point2);
+
+grn_rc
 grn_selector_geo_in_circle(grn_ctx *ctx, grn_obj *obj, grn_obj **args, int nargs,
                            grn_obj *res, grn_operator op)
 {
-  if (nargs == 4) {
-    grn_obj *center_point, *distance;
-    center_point = args[2];
-    distance = args[3];
-    grn_geo_select_in_circle(ctx, obj, center_point, distance, res, op);
-  } else {
+  grn_geo_approximate_type type = GRN_GEO_APPROXIMATE_RECTANGLE;
+
+  switch (nargs) {
+  case 5 :
+    if (grn_geo_resolve_approximate_type(ctx, args[4], &type) != GRN_SUCCESS) {
+      break;
+    }
+    /* fallthru */
+  case 4 :
+    {
+      grn_obj *center_point, *distance;
+      center_point = args[2];
+      distance = args[3];
+      grn_geo_select_in_circle(ctx, obj, center_point, distance, type, res, op);
+    }
+    break;
+  default :
     ERR(GRN_INVALID_ARGUMENT,
-        "geo_in_circle(): requires 3 arguments but was <%d> arguments",
+        "geo_in_circle(): requires 3 or 4 arguments but was <%d> arguments",
         nargs - 1);
+    break;
   }
+
   return ctx->rc;
 }
 
 grn_rc
 grn_geo_select_in_circle(grn_ctx *ctx, grn_obj *index,
                          grn_obj *center_point, grn_obj *distance,
+                         grn_geo_approximate_type approximate_type,
                          grn_obj *res, grn_operator op)
 {
   grn_id domain;
   double center_longitude, center_latitude;
-  double on_circle_longitude, on_circle_latitude;
-  double x, y, d;
+  double d;
   grn_obj *pat, *point_on_circle = NULL, center_point_, point_on_circle_;
   grn_geo_point *center, on_circle;
+  grn_geo_distance_raw_func distance_raw_func;
   pat = grn_ctx_at(ctx, index->header.domain);
   domain = pat->header.domain;
   if (domain != GRN_DB_TOKYO_GEO_POINT && domain != GRN_DB_WGS84_GEO_POINT) {
@@ -688,36 +742,53 @@ grn_geo_select_in_circle(grn_ctx *ctx, grn_obj *index,
   center = GRN_GEO_POINT_VALUE_RAW(center_point);
   center_longitude = GRN_GEO_INT2RAD(center->longitude);
   center_latitude = GRN_GEO_INT2RAD(center->latitude);
+
+  switch (approximate_type) {
+  case GRN_GEO_APPROXIMATE_RECTANGLE :
+    distance_raw_func = grn_geo_distance_rectangle_raw;
+    break;
+  case GRN_GEO_APPROXIMATE_SPHERE :
+    distance_raw_func = grn_geo_distance_sphere_raw;
+    break;
+  case GRN_GEO_APPROXIMATE_ELLIPSOID :
+    if (domain == GRN_DB_WGS84_GEO_POINT) {
+      distance_raw_func = grn_geo_distance_ellipsoid_raw_wgs84;
+    } else {
+      distance_raw_func = grn_geo_distance_ellipsoid_raw_tokyo;
+    }
+    break;
+  default :
+    ERR(GRN_INVALID_ARGUMENT,
+        "unknown approximate type: <%d>", approximate_type);
+    goto exit;
+    break;
+  }
+
   switch (distance->header.domain) {
   case GRN_DB_INT32 :
-    d = GRN_INT32_VALUE(distance) / (double)GRN_GEO_RADIUS;
-    on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d);
+    d = GRN_INT32_VALUE(distance);
+    on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d / (double)GRN_GEO_RADIUS);
     on_circle.longitude = center->longitude;
-    d = d * d;
     break;
   case GRN_DB_UINT32 :
-    d = GRN_UINT32_VALUE(distance) / (double)GRN_GEO_RADIUS;
-    on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d);
+    d = GRN_UINT32_VALUE(distance);
+    on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d / (double)GRN_GEO_RADIUS);
     on_circle.longitude = center->longitude;
-    d = d * d;
     break;
   case GRN_DB_INT64 :
-    d = GRN_INT64_VALUE(distance) / (double)GRN_GEO_RADIUS;
-    on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d);
+    d = GRN_INT64_VALUE(distance);
+    on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d / (double)GRN_GEO_RADIUS);
     on_circle.longitude = center->longitude;
-    d = d * d;
     break;
   case GRN_DB_UINT64 :
-    d = GRN_UINT64_VALUE(distance) / (double)GRN_GEO_RADIUS;
-    on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d);
+    d = GRN_UINT64_VALUE(distance);
+    on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d / (double)GRN_GEO_RADIUS);
     on_circle.longitude = center->longitude;
-    d = d * d;
     break;
   case GRN_DB_FLOAT :
-    d = GRN_FLOAT_VALUE(distance) / (double)GRN_GEO_RADIUS;
-    on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d);
+    d = GRN_FLOAT_VALUE(distance);
+    on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d / (double)GRN_GEO_RADIUS);
     on_circle.longitude = center->longitude;
-    d = d * d;
     break;
   case GRN_DB_SHORT_TEXT :
   case GRN_DB_TEXT :
@@ -732,12 +803,7 @@ grn_geo_select_in_circle(grn_ctx *ctx, grn_obj *index,
     if (!point_on_circle) { point_on_circle = distance; }
     GRN_GEO_POINT_VALUE(point_on_circle,
                         on_circle.latitude, on_circle.longitude);
-    on_circle_longitude = GRN_GEO_INT2RAD(on_circle.longitude);
-    on_circle_latitude = GRN_GEO_INT2RAD(on_circle.latitude);
-    x = (on_circle_longitude - center_longitude) *
-      cos((center_latitude + on_circle_latitude) * 0.5);
-    y = (on_circle_latitude - center_latitude);
-    d = ((x * x) + (y * y));
+    d = distance_raw_func(ctx, center, &on_circle);
     if (point_on_circle == &point_on_circle_) {
       grn_obj_unlink(ctx, point_on_circle);
     }
@@ -781,16 +847,12 @@ grn_geo_select_in_circle(grn_ctx *ctx, grn_obj *index,
       if (tc) {
         grn_id tid;
         grn_geo_point point;
-        double longitude, latitude;
         while ((tid = grn_table_cursor_next(ctx, tc))) {
+          double point_distance;
           grn_table_get_key(ctx, pat, tid, &point, sizeof(grn_geo_point));
-          longitude = GRN_GEO_INT2RAD(point.longitude);
-          latitude = GRN_GEO_INT2RAD(point.latitude);
-          x = (center_longitude - longitude) *
-            cos((latitude + center_latitude) * 0.5);
-          y = (center_latitude - latitude);
-          if (((x * x) + (y * y)) <= d) {
-            inspect_tid(ctx, tid, &point, (x * x) + (y * y));
+          point_distance = distance_raw_func(ctx, &point, center);
+          if (point_distance <= d) {
+            inspect_tid(ctx, tid, &point, point_distance);
             grn_ii_at(ctx, (grn_ii *)index, tid, (grn_hash *)res, op);
           }
         }
@@ -1787,6 +1849,28 @@ grn_geo_distance_ellipsoid_raw(grn_ctx *ctx,
 }
 
 double
+grn_geo_distance_ellipsoid_raw_tokyo(grn_ctx *ctx,
+                                     grn_geo_point *point1,
+                                     grn_geo_point *point2)
+{
+  return grn_geo_distance_ellipsoid_raw(ctx, point1, point2,
+                                        GRN_GEO_BES_C1,
+                                        GRN_GEO_BES_C2,
+                                        GRN_GEO_BES_C3);
+}
+
+double
+grn_geo_distance_ellipsoid_raw_wgs84(grn_ctx *ctx,
+                                     grn_geo_point *point1,
+                                     grn_geo_point *point2)
+{
+  return grn_geo_distance_ellipsoid_raw(ctx, point1, point2,
+                                        GRN_GEO_GRS_C1,
+                                        GRN_GEO_GRS_C2,
+                                        GRN_GEO_GRS_C3);
+}
+
+double
 grn_geo_distance(grn_ctx *ctx, grn_obj *point1, grn_obj *point2,
                  grn_geo_approximate_type type)
 {
@@ -1900,19 +1984,13 @@ grn_geo_distance_ellipsoid(grn_ctx *ctx, grn_obj *point1, grn_obj *point2)
       point2 = &point2_;
     }
     if (domain == GRN_DB_TOKYO_GEO_POINT) {
-      d = grn_geo_distance_ellipsoid_raw(ctx,
-                                         GRN_GEO_POINT_VALUE_RAW(point1),
-                                         GRN_GEO_POINT_VALUE_RAW(point2),
-                                         GRN_GEO_BES_C1,
-                                         GRN_GEO_BES_C2,
-                                         GRN_GEO_BES_C3);
+      d = grn_geo_distance_ellipsoid_raw_tokyo(ctx,
+                                               GRN_GEO_POINT_VALUE_RAW(point1),
+                                               GRN_GEO_POINT_VALUE_RAW(point2));
     } else {
-      d = grn_geo_distance_ellipsoid_raw(ctx,
-                                         GRN_GEO_POINT_VALUE_RAW(point1),
-                                         GRN_GEO_POINT_VALUE_RAW(point2),
-                                         GRN_GEO_GRS_C1,
-                                         GRN_GEO_GRS_C2,
-                                         GRN_GEO_GRS_C3);
+      d = grn_geo_distance_ellipsoid_raw_wgs84(ctx,
+                                               GRN_GEO_POINT_VALUE_RAW(point1),
+                                               GRN_GEO_POINT_VALUE_RAW(point2));
     }
   } else {
     /* todo */

  Modified: lib/geo.h (+37 -0)
===================================================================
--- lib/geo.h    2011-12-28 14:44:11 +0900 (d2d285e)
+++ lib/geo.h    2011-12-28 15:00:14 +0900 (0a0ee5d)
@@ -102,6 +102,37 @@ grn_rc grn_geo_cursor_close(grn_ctx *ctx, grn_obj *geo_cursor);
 int grn_geo_table_sort(grn_ctx *ctx, grn_obj *table, int offset, int limit,
                        grn_obj *result, grn_table_sort_key *keys, int n_keys);
 
+grn_rc grn_geo_resolve_approximate_type(grn_ctx *ctx, grn_obj *type_name,
+                                        grn_geo_approximate_type *type);
+
+/**
+ * grn_geo_select_in_circle:
+ * @index: the index column for TokyoGeoPoint or WGS84GeoPpoint type.
+ * @center_point: the center point of the target circle. (ShortText, Text,
+ * LongText, TokyoGeoPoint or WGS84GeoPoint)
+ * @distance: the radius of the target circle (Int32,
+ * UInt32, Int64, UInt64 or Float) or the point
+ * on the circumference of the target circle. (ShortText, Text, LongText,
+ * TokyoGeoPoint or WGS84GeoPoint)
+ * @approximate_type: the approximate type to compute
+ * distance.
+ * @res: the table to store found record IDs. It must be
+ * GRN_TABLE_HASH_KEY type table.
+ * @op: the operator for matched records.
+ *
+ * It selects records that are in the circle specified by
+ * @center_point and @distance from @center_point. Records
+ * are searched by @index. Found records are added to @res
+ * table with @op operation.
+ **/
+grn_rc grn_geo_select_in_circle(grn_ctx *ctx,
+                                grn_obj *index,
+                                grn_obj *center_point,
+                                grn_obj *distance,
+                                grn_geo_approximate_type approximate_type,
+                                grn_obj *res,
+                                grn_operator op);
+
 grn_rc grn_selector_geo_in_circle(grn_ctx *ctx, grn_obj *obj, grn_obj **args,
                                   int nargs, grn_obj *res, grn_operator op);
 grn_rc grn_selector_geo_in_rectangle(grn_ctx *ctx, grn_obj *obj, grn_obj **args,
@@ -129,6 +160,12 @@ double grn_geo_distance_ellipsoid_raw(grn_ctx *ctx,
                                       grn_geo_point *point1,
                                       grn_geo_point *point2,
                                       int c1, int c2, double c3);
+double grn_geo_distance_ellipsoid_raw_tokyo(grn_ctx *ctx,
+                                            grn_geo_point *point1,
+                                            grn_geo_point *point2);
+double grn_geo_distance_ellipsoid_raw_wgs84(grn_ctx *ctx,
+                                            grn_geo_point *point1,
+                                            grn_geo_point *point2);
 
 #ifdef __cplusplus
 }

  Modified: lib/proc.c (+1 -37)
===================================================================
--- lib/proc.c    2011-12-28 14:44:11 +0900 (8b07cda)
+++ lib/proc.c    2011-12-28 15:00:14 +0900 (3990b36)
@@ -2495,42 +2495,6 @@ func_now(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
   return obj;
 }
 
-static grn_rc
-geo_resolve_approximate_type(grn_ctx *ctx, grn_obj *type_name,
-                             grn_geo_approximate_type *type)
-{
-  grn_rc rc;
-  grn_obj approximate_type;
-
-  GRN_TEXT_INIT(&approximate_type, 0);
-  rc = grn_obj_cast(ctx, type_name, &approximate_type, GRN_FALSE);
-  if (rc == GRN_SUCCESS) {
-    const char *name;
-    unsigned int size;
-    name = GRN_TEXT_VALUE(&approximate_type);
-    size = GRN_TEXT_LEN(&approximate_type);
-    if ((strncmp("rectangle", name, size) == 0) ||
-        (strncmp("rect", name, size) == 0)) {
-      *type = GRN_GEO_APPROXIMATE_RECTANGLE;
-    } else if ((strncmp("sphere", name, size) == 0) ||
-               (strncmp("sphr", name, size) == 0)) {
-      *type = GRN_GEO_APPROXIMATE_SPHERE;
-    } else if ((strncmp("ellipsoid", name, size) == 0) ||
-               (strncmp("ellip", name, size) == 0)) {
-      *type = GRN_GEO_APPROXIMATE_ELLIPSOID;
-    } else {
-      ERR(GRN_INVALID_ARGUMENT,
-          "geo distance approximate type must be one of "
-          "[rectangle, rect, sphere, sphr, ellipsoid, ellip]"
-          ": <%.*s>",
-          size, name);
-    }
-  }
-  GRN_OBJ_FIN(ctx, &approximate_type);
-
-  return rc;
-}
-
 static grn_obj *
 func_geo_in_circle(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
 {
@@ -2567,7 +2531,7 @@ func_geo_distance(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_d
   grn_geo_approximate_type type = GRN_GEO_APPROXIMATE_RECTANGLE;
   switch (nargs) {
   case 3 :
-    if (geo_resolve_approximate_type(ctx, args[2], &type) != GRN_SUCCESS) {
+    if (grn_geo_resolve_approximate_type(ctx, args[2], &type) != GRN_SUCCESS) {
       break;
     }
     /* fallthru */

  Modified: test/unit/core/test-command-select-geo.c (+15 -6)
===================================================================
--- test/unit/core/test-command-select-geo.c    2011-12-28 14:44:11 +0900 (c4fc270)
+++ test/unit/core/test-command-select-geo.c    2011-12-28 15:00:14 +0900 (c2ed3da)
@@ -145,7 +145,9 @@ test_rectangle(gconstpointer data)
   gdouble yurakucho_latitude = 35.67487;
   gdouble yurakucho_longitude = 139.76352;
   gint distance = 3 * 1000;
+  const gchar *approximate_type;
 
+  approximate_type = gcut_data_get_string(data, "approximate-type");
   cut_assert_equal_string(
     "[[[7],"
     "[[\"name\",\"ShortText\"],[\"_score\",\"Int32\"],"
@@ -163,13 +165,14 @@ test_rectangle(gconstpointer data)
         "select Shops "
         "--sortby '+_score, +name' "
         "--output_columns 'name, _score, location' "
-        "--filter 'geo_in_circle(location, \"%s\", %d)' "
+        "--filter 'geo_in_circle(location, \"%s\", %d, \"%s\")' "
         "--scorer "
           "'_score = geo_distance(location, \"%s\", \"%s\") * 1000 * 1000'",
         grn_test_location_string(yurakucho_latitude, yurakucho_longitude),
         distance,
+        approximate_type,
         grn_test_location_string(yurakucho_latitude, yurakucho_longitude),
-        gcut_data_get_string(data, "approximate-type"))));
+        approximate_type)));
 }
 
 void
@@ -192,7 +195,9 @@ test_sphere(gconstpointer data)
   gdouble yurakucho_latitude = 35.67487;
   gdouble yurakucho_longitude = 139.76352;
   gint distance = 3 * 1000;
+  const gchar *approximate_type;
 
+  approximate_type = gcut_data_get_string(data, "approximate-type");
   cut_assert_equal_string(
     "[[[7],"
     "[[\"name\",\"ShortText\"],[\"_score\",\"Int32\"],"
@@ -210,13 +215,14 @@ test_sphere(gconstpointer data)
         "select Shops "
         "--sortby '+_score, +name' "
         "--output_columns 'name, _score, location' "
-        "--filter 'geo_in_circle(location, \"%s\", %d)' "
+        "--filter 'geo_in_circle(location, \"%s\", %d, \"%s\")' "
         "--scorer "
           "'_score = geo_distance(location, \"%s\", \"%s\") * 1000 * 1000'",
         grn_test_location_string(yurakucho_latitude, yurakucho_longitude),
         distance,
+        approximate_type,
         grn_test_location_string(yurakucho_latitude, yurakucho_longitude),
-        gcut_data_get_string(data, "approximate-type"))));
+        approximate_type)));
 }
 
 void
@@ -239,7 +245,9 @@ test_ellipsoid(gconstpointer data)
   gdouble yurakucho_latitude = 35.67487;
   gdouble yurakucho_longitude = 139.76352;
   gint distance = 3 * 1000;
+  const gchar *approximate_type;
 
+  approximate_type = gcut_data_get_string(data, "approximate-type");
   cut_assert_equal_string(
     "[[[7],"
     "[[\"name\",\"ShortText\"],[\"_score\",\"Int32\"],"
@@ -257,11 +265,12 @@ test_ellipsoid(gconstpointer data)
         "select Shops "
         "--sortby '+_score, +name' "
         "--output_columns 'name, _score, location' "
-        "--filter 'geo_in_circle(location, \"%s\", %d)' "
+        "--filter 'geo_in_circle(location, \"%s\", %d, \"%s\")' "
         "--scorer "
           "'_score = geo_distance(location, \"%s\", \"%s\") * 1000 * 1000'",
         grn_test_location_string(yurakucho_latitude, yurakucho_longitude),
         distance,
+        approximate_type,
         grn_test_location_string(yurakucho_latitude, yurakucho_longitude),
-        gcut_data_get_string(data, "approximate-type"))));
+        approximate_type)));
 }




Groonga-commit メーリングリストの案内
Back to archive index