diff options
Diffstat (limited to 'storage/mroonga/vendor/groonga/lib/geo.c')
-rw-r--r-- | storage/mroonga/vendor/groonga/lib/geo.c | 2788 |
1 files changed, 2788 insertions, 0 deletions
diff --git a/storage/mroonga/vendor/groonga/lib/geo.c b/storage/mroonga/vendor/groonga/lib/geo.c new file mode 100644 index 00000000..26aca445 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/geo.c @@ -0,0 +1,2788 @@ +/* -*- c-basic-offset: 2 -*- */ +/* + Copyright(C) 2009-2017 Brazil + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA +*/ + +#include "grn_geo.h" +#include "grn_pat.h" +#include "grn_util.h" + +#include <string.h> +#include <stdlib.h> + +#define GRN_GEO_POINT_IN_NORTH_EAST(point) \ + ((point)->latitude >= 0 && (point)->longitude >= 0) +#define GRN_GEO_POINT_IN_NORTH_WEST(point) \ + ((point)->latitude >= 0 && (point)->longitude < 0) +#define GRN_GEO_POINT_IN_SOUTH_WEST(point) \ + ((point)->latitude < 0 && (point)->longitude < 0) +#define GRN_GEO_POINT_IN_SOUTH_EAST(point) \ + ((point)->latitude < 0 && (point)->longitude >= 0) + +#define GRN_GEO_LONGITUDE_IS_WRAPPED(top_left, bottom_right) \ + ((top_left)->longitude > 0 && (bottom_right)->longitude < 0) + +typedef struct { + grn_id id; + double d; +} geo_entry; + +typedef struct +{ + grn_geo_point key; + int key_size; +} mesh_entry; + +typedef struct { + grn_obj *pat; + grn_obj top_left_point_buffer; + grn_obj bottom_right_point_buffer; + grn_geo_point *top_left; + grn_geo_point *bottom_right; +} in_rectangle_data; + +typedef struct { + grn_geo_point min; + grn_geo_point max; + int rectangle_common_bit; + uint8_t rectangle_common_key[sizeof(grn_geo_point)]; +} in_rectangle_area_data; + +static int +compute_diff_bit(uint8_t *geo_key1, uint8_t *geo_key2) +{ + int i, j, diff_bit = 0; + + for (i = 0; i < sizeof(grn_geo_point); i++) { + if (geo_key1[i] != geo_key2[i]) { + diff_bit = 8; + for (j = 0; j < 8; j++) { + if ((geo_key1[i] & (1 << (7 - j))) != (geo_key2[i] & (1 << (7 - j)))) { + diff_bit = j; + break; + } + } + break; + } + } + + return i * 8 + diff_bit; +} + +static void +compute_min_and_max_key(uint8_t *key_base, int diff_bit, + uint8_t *key_min, uint8_t *key_max) +{ + int diff_byte, diff_bit_mask; + + diff_byte = diff_bit / 8; + diff_bit_mask = 0xff >> (diff_bit % 8); + + if (diff_byte == sizeof(grn_geo_point)) { + if (key_min) { grn_memcpy(key_min, key_base, diff_byte); } + if (key_max) { grn_memcpy(key_max, key_base, diff_byte); } + } else { + if (key_min) { + grn_memcpy(key_min, key_base, diff_byte + 1); + key_min[diff_byte] &= ~diff_bit_mask; + memset(key_min + diff_byte + 1, 0, + sizeof(grn_geo_point) - diff_byte - 1); + } + + if (key_max) { + grn_memcpy(key_max, key_base, diff_byte + 1); + key_max[diff_byte] |= diff_bit_mask; + memset(key_max + diff_byte + 1, 0xff, + sizeof(grn_geo_point) - diff_byte - 1); + } + } +} + +static void +compute_min_and_max(grn_geo_point *base_point, int diff_bit, + grn_geo_point *geo_min, grn_geo_point *geo_max) +{ + uint8_t geo_key_base[sizeof(grn_geo_point)]; + uint8_t geo_key_min[sizeof(grn_geo_point)]; + uint8_t geo_key_max[sizeof(grn_geo_point)]; + + grn_gton(geo_key_base, base_point, sizeof(grn_geo_point)); + compute_min_and_max_key(geo_key_base, diff_bit, + geo_min ? geo_key_min : NULL, + geo_max ? geo_key_max : NULL); + if (geo_min) { + grn_ntog((uint8_t *)geo_min, geo_key_min, sizeof(grn_geo_point)); + } + if (geo_max) { + grn_ntog((uint8_t *)geo_max, geo_key_max, sizeof(grn_geo_point)); + } +} + +/* #define GEO_DEBUG */ + +#ifdef GEO_DEBUG +#include <stdio.h> + +static void +inspect_mesh(grn_ctx *ctx, grn_geo_point *key, int key_size, int n) +{ + grn_geo_point min, max; + + printf("mesh: %d:%d\n", n, key_size); + + printf("key: "); + grn_p_geo_point(ctx, key); + + compute_min_and_max(key, key_size, &min, &max); + printf("min: "); + grn_p_geo_point(ctx, &min); + printf("max: "); + grn_p_geo_point(ctx, &max); +} + +static void +inspect_mesh_entry(grn_ctx *ctx, mesh_entry *entries, int n) +{ + mesh_entry *entry; + + entry = entries + n; + inspect_mesh(ctx, &(entry->key), entry->key_size, n); +} + +static void +inspect_tid(grn_ctx *ctx, grn_id tid, grn_geo_point *point, double d) +{ + printf("tid: %d:%g", tid, d); + grn_p_geo_point(ctx, point); +} + +static void +inspect_key(grn_ctx *ctx, uint8_t *key) +{ + int i; + for (i = 0; i < 8; i++) { + int j; + for (j = 0; j < 8; j++) { + printf("%d", (key[i] & (1 << (7 - j))) >> (7 - j)); + } + printf(" "); + } + printf("\n"); +} + +static void +print_key_mark(grn_ctx *ctx, int target_bit) +{ + int i; + + for (i = 0; i < target_bit; i++) { + printf(" "); + if (i > 0 && i % 8 == 0) { + printf(" "); + } + } + if (i > 0 && i % 8 == 0) { + printf(" "); + } + printf("^\n"); +} + +static void +inspect_cursor_entry(grn_ctx *ctx, grn_geo_cursor_entry *entry) +{ + grn_geo_point point; + + printf("entry: "); + grn_ntog((uint8_t *)&point, entry->key, sizeof(grn_geo_point)); + grn_p_geo_point(ctx, &point); + inspect_key(ctx, entry->key); + print_key_mark(ctx, entry->target_bit); + + printf(" target bit: %d\n", entry->target_bit); + +#define INSPECT_STATUS_FLAG(name) \ + ((entry->status_flags & GRN_GEO_CURSOR_ENTRY_STATUS_ ## name) ? "true" : "false") + + printf(" top included: %s\n", INSPECT_STATUS_FLAG(TOP_INCLUDED)); + printf("bottom included: %s\n", INSPECT_STATUS_FLAG(BOTTOM_INCLUDED)); + printf(" left included: %s\n", INSPECT_STATUS_FLAG(LEFT_INCLUDED)); + printf(" right included: %s\n", INSPECT_STATUS_FLAG(RIGHT_INCLUDED)); + printf(" latitude inner: %s\n", INSPECT_STATUS_FLAG(LATITUDE_INNER)); + printf("longitude inner: %s\n", INSPECT_STATUS_FLAG(LONGITUDE_INNER)); + +#undef INSPECT_STATUS_FLAG +} + +static void +inspect_cursor_entry_targets(grn_ctx *ctx, grn_geo_cursor_entry *entry, + uint8_t *top_left_key, uint8_t *bottom_right_key, + grn_geo_cursor_entry *next_entry0, + grn_geo_cursor_entry *next_entry1) +{ + printf("entry: "); + inspect_key(ctx, entry->key); + printf("top-left: "); + inspect_key(ctx, top_left_key); + printf("bottom-right: "); + inspect_key(ctx, bottom_right_key); + printf("next-entry-0: "); + inspect_key(ctx, next_entry0->key); + printf("next-entry-1: "); + inspect_key(ctx, next_entry1->key); + printf(" "); + print_key_mark(ctx, entry->target_bit + 1); +} +#else +# define inspect_mesh(...) +# define inspect_mesh_entry(...) +# define inspect_tid(...) +# define inspect_key(...) +# define print_key_mark(...) +# define inspect_cursor_entry(...) +# define inspect_cursor_entry_targets(...) +#endif + +static int +grn_geo_table_sort_detect_far_point(grn_ctx *ctx, grn_obj *table, grn_obj *index, + grn_pat *pat, geo_entry *entries, + grn_pat_cursor *pc, int n, + grn_bool accessorp, + grn_geo_point *base_point, + double *d_far, int *diff_bit) +{ + int i = 0, diff_bit_prev, diff_bit_current; + grn_id tid; + geo_entry *ep, *p; + double d; + uint8_t geo_key_prev[sizeof(grn_geo_point)]; + uint8_t geo_key_curr[sizeof(grn_geo_point)]; + grn_geo_point point; + + *d_far = 0.0; + grn_gton(geo_key_curr, base_point, sizeof(grn_geo_point)); + *diff_bit = sizeof(grn_geo_point) * 8; + diff_bit_current = sizeof(grn_geo_point) * 8; + grn_memcpy(&point, base_point, sizeof(grn_geo_point)); + ep = entries; + inspect_mesh(ctx, &point, *diff_bit, -1); + while ((tid = grn_pat_cursor_next(ctx, pc))) { + grn_ii_cursor *ic = grn_ii_cursor_open(ctx, (grn_ii *)index, tid, 0, 0, 1, 0); + if (ic) { + grn_posting *posting; + grn_gton(geo_key_prev, &point, sizeof(grn_geo_point)); + grn_pat_get_key(ctx, pat, tid, &point, sizeof(grn_geo_point)); + grn_gton(geo_key_curr, &point, sizeof(grn_geo_point)); + d = grn_geo_distance_rectangle_raw(ctx, base_point, &point); + inspect_tid(ctx, tid, &point, d); + + diff_bit_prev = diff_bit_current; + diff_bit_current = compute_diff_bit(geo_key_curr, geo_key_prev); +#ifdef GEO_DEBUG + printf("diff: %d:%d:%d\n", *diff_bit, diff_bit_prev, diff_bit_current); +#endif + if ((diff_bit_current % 2) == 1) { + diff_bit_current--; + } + if (diff_bit_current < diff_bit_prev && *diff_bit > diff_bit_current) { + if (i == n) { + grn_ii_cursor_close(ctx, ic); + break; + } + *diff_bit = diff_bit_current; + } + + if (d > *d_far) { + *d_far = d; + } + while ((posting = grn_ii_cursor_next(ctx, ic))) { + grn_id rid = accessorp + ? grn_table_get(ctx, table, &posting->rid, sizeof(grn_id)) + : posting->rid; + if (rid) { + for (p = ep; entries < p && p[-1].d > d; p--) { + p->id = p[-1].id; + p->d = p[-1].d; + } + p->id = rid; + p->d = d; + if (i < n) { + ep++; + i++; + } + } + } + grn_ii_cursor_close(ctx, ic); + } + } + + return i; +} + +typedef enum { + MESH_LEFT_TOP, + MESH_RIGHT_TOP, + MESH_RIGHT_BOTTOM, + MESH_LEFT_BOTTOM +} mesh_position; + +/* + meshes should have + 86 >= spaces when include_base_point_hash == GRN_FALSE, + 87 >= spaces when include_base_point_hash == GRN_TRUE. +*/ +static int +grn_geo_get_meshes_for_circle(grn_ctx *ctx, grn_geo_point *base_point, + double d_far, int diff_bit, + int include_base_point_mesh, + mesh_entry *meshes) +{ + double d; + int n_meshes; + int lat_diff, lng_diff; + mesh_position position; + grn_geo_point geo_base, geo_min, geo_max; + + compute_min_and_max(base_point, diff_bit - 2, &geo_min, &geo_max); + + lat_diff = (geo_max.latitude - geo_min.latitude + 1) / 2; + lng_diff = (geo_max.longitude - geo_min.longitude + 1) / 2; + geo_base.latitude = geo_min.latitude + lat_diff; + geo_base.longitude = geo_min.longitude + lng_diff; + if (base_point->latitude >= geo_base.latitude) { + if (base_point->longitude >= geo_base.longitude) { + position = MESH_RIGHT_TOP; + } else { + position = MESH_LEFT_TOP; + } + } else { + if (base_point->longitude >= geo_base.longitude) { + position = MESH_RIGHT_BOTTOM; + } else { + position = MESH_LEFT_BOTTOM; + } + } + /* + base_point: b + geo_min: i + geo_max: a + geo_base: x: must be at the left bottom in the top right mesh. + + e.g.: base_point is at the left bottom mesh case: + +------+------+ + | | a| + | |x | + ^+------+------+ + || | | + lng_diff || b | | + \/i------+------+ + <------> + lat_diff + + grn_min + lat_diff -> the right mesh. + grn_min + lng_diff -> the top mesh. + */ +#ifdef GEO_DEBUG + grn_p_geo_point(ctx, base_point); + printf("base: "); + grn_p_geo_point(ctx, &geo_base); + printf("min: "); + grn_p_geo_point(ctx, &geo_min); + printf("max: "); + grn_p_geo_point(ctx, &geo_max); + printf("diff: %d (%d, %d)\n", diff_bit, lat_diff, lng_diff); + switch (position) { + case MESH_LEFT_TOP : + printf("position: left-top\n"); + break; + case MESH_RIGHT_TOP : + printf("position: right-top\n"); + break; + case MESH_RIGHT_BOTTOM : + printf("position: right-bottom\n"); + break; + case MESH_LEFT_BOTTOM : + printf("position: left-bottom\n"); + break; + } +#endif + + n_meshes = 0; + +#define add_mesh(lat_diff_,lng_diff_,key_size_) do {\ + meshes[n_meshes].key.latitude = geo_base.latitude + (lat_diff_);\ + meshes[n_meshes].key.longitude = geo_base.longitude + (lng_diff_);\ + meshes[n_meshes].key_size = key_size_;\ + n_meshes++;\ +} while (0) + + if (include_base_point_mesh || position != MESH_LEFT_TOP) { + add_mesh(0, -lng_diff, diff_bit); + } + if (include_base_point_mesh || position != MESH_RIGHT_TOP) { + add_mesh(0, 0, diff_bit); + } + if (include_base_point_mesh || position != MESH_RIGHT_BOTTOM) { + add_mesh(-lat_diff, 0, diff_bit); + } + if (include_base_point_mesh || position != MESH_LEFT_BOTTOM) { + add_mesh(-lat_diff, -lng_diff, diff_bit); + } + + /* + b: base_point + x: geo_base + 0-83: sub meshes. 0-83 are added order. + + j: -5 -4 -3 -2 -1 0 1 2 3 4 + +---+---+---+---+---+---+---+---+---+---+ + |74 |75 |76 |77 |78 |79 |80 |81 |82 |83 | 4 + +---+---+---+---+---+---+---+---+---+---+ + |64 |65 |66 |67 |68 |69 |70 |71 |72 |73 | 3 + +---+---+---+---+---+---+---+---+---+---+ + |54 |55 |56 |57 |58 |59 |60 |61 |62 |63 | 2 + +---+---+---+---+---+---+---+---+---+---+ + |48 |49 |50 | b | |51 |52 |53 | 1 + +---+---+---+ | +---+---+---+ + |42 |43 |44 | |x |45 |46 |47 | 0 + +---+---+---+-------+-------+---+---+---+ + |36 |37 |38 | | |39 |40 |41 | -1 + +---+---+---+ base meshes +---+---+---+ + |30 |31 |32 | | |33 |34 |35 | -2 + +---+---+---+---+---+---+---+---+---+---+ + |20 |21 |22 |23 |24 |25 |26 |27 |28 |29 | -3 + +---+---+---+---+---+---+---+---+---+---+ + |10 |11 |12 |13 |14 |15 |16 |17 |18 |19 | -4 + +---+---+---+---+---+---+---+---+---+---+ + | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -5 + +---+---+---+---+---+---+---+---+---+---+ + i + */ + { + int i, j, n_sub_meshes, lat, lat_min, lat_max, lng, lng_min, lng_max; + n_sub_meshes = 0; + for (i = -5; i < 5; i++) { + lat_min = ((lat_diff + 1) / 2) * i; + lat_max = ((lat_diff + 1) / 2) * (i + 1) - 1; + for (j = -5; j < 5; j++) { + if (-3 < i && i < 2 && -3 < j && j < 2) { + continue; + } + lng_min = ((lng_diff + 1) / 2) * j; + lng_max = ((lng_diff + 1) / 2) * (j + 1) - 1; + if (base_point->latitude <= geo_base.latitude + lat_min) { + lat = geo_base.latitude + lat_min; + } else if (geo_base.latitude + lat_max < base_point->latitude) { + lat = geo_base.latitude + lat_max; + } else { + lat = base_point->latitude; + } + if (base_point->longitude <= geo_base.longitude + lng_min) { + lng = geo_base.longitude + lng_min; + } else if (geo_base.longitude + lng_max < base_point->longitude) { + lng = geo_base.longitude + lng_max; + } else { + lng = base_point->longitude; + } + meshes[n_meshes].key.latitude = lat; + meshes[n_meshes].key.longitude = lng; + d = grn_geo_distance_rectangle_raw(ctx, base_point, + &(meshes[n_meshes].key)); + if (d < d_far) { +#ifdef GEO_DEBUG + printf("sub-mesh: %d: (%d,%d): (%d,%d;%d,%d)\n", + n_sub_meshes, base_point->latitude, base_point->longitude, + geo_base.latitude + lat_min, + geo_base.latitude + lat_max, + geo_base.longitude + lng_min, + geo_base.longitude + lng_max); + grn_p_geo_point(ctx, &(meshes[n_meshes].key)); +#endif + meshes[n_meshes].key_size = diff_bit + 2; + n_meshes++; + } + n_sub_meshes++; + } + } + } + +#undef add_mesh + + return n_meshes; +} + +static int +grn_geo_table_sort_collect_points(grn_ctx *ctx, grn_obj *table, grn_obj *index, + grn_pat *pat, + geo_entry *entries, int n_entries, + int n, grn_bool accessorp, + grn_geo_point *base_point, + double d_far, int diff_bit) +{ + int n_meshes; + mesh_entry meshes[86]; + geo_entry *ep, *p; + + n_meshes = grn_geo_get_meshes_for_circle(ctx, base_point, d_far, diff_bit, + GRN_FALSE, meshes); + + ep = entries + n_entries; + while (n_meshes--) { + grn_id tid; + grn_pat_cursor *pc = grn_pat_cursor_open(ctx, pat, + &(meshes[n_meshes].key), + meshes[n_meshes].key_size, + NULL, 0, + 0, -1, + GRN_CURSOR_PREFIX|GRN_CURSOR_SIZE_BY_BIT); + inspect_mesh_entry(ctx, meshes, n_meshes); + if (pc) { + while ((tid = grn_pat_cursor_next(ctx, pc))) { + grn_ii_cursor *ic = grn_ii_cursor_open(ctx, (grn_ii *)index, tid, 0, 0, 1, 0); + if (ic) { + double d; + grn_geo_point pos; + grn_posting *posting; + grn_pat_get_key(ctx, pat, tid, &pos, sizeof(grn_geo_point)); + d = grn_geo_distance_rectangle_raw(ctx, base_point, &pos); + inspect_tid(ctx, tid, &pos, d); + while ((posting = grn_ii_cursor_next(ctx, ic))) { + grn_id rid = accessorp + ? grn_table_get(ctx, table, &posting->rid, sizeof(grn_id)) + : posting->rid; + if (rid) { + for (p = ep; entries < p && p[-1].d > d; p--) { + p->id = p[-1].id; + p->d = p[-1].d; + } + p->id = rid; + p->d = d; + if (n_entries < n) { + ep++; + n_entries++; + } + } + } + grn_ii_cursor_close(ctx, ic); + } + } + grn_pat_cursor_close(ctx, pc); + } + } + return n_entries; +} + +static inline grn_obj * +find_geo_sort_index(grn_ctx *ctx, grn_obj *key) +{ + grn_obj *index = NULL; + + if (GRN_ACCESSORP(key)) { + grn_accessor *accessor = (grn_accessor *)key; + if (accessor->action != GRN_ACCESSOR_GET_KEY) { + return NULL; + } + if (!(DB_OBJ(accessor->obj)->id & GRN_OBJ_TMP_OBJECT)) { + return NULL; + } + if (accessor->obj->header.type != GRN_TABLE_HASH_KEY) { + return NULL; + } + if (!accessor->next) { + return NULL; + } + grn_column_index(ctx, accessor->next->obj, GRN_OP_LESS, &index, 1, NULL); + } else { + grn_column_index(ctx, key, GRN_OP_LESS, &index, 1, NULL); + } + + return index; +} + +static inline int +grn_geo_table_sort_by_distance(grn_ctx *ctx, + grn_obj *table, + grn_obj *index, + grn_pat *pat, + grn_pat_cursor *pc, + grn_bool accessorp, + grn_geo_point *base_point, + int offset, + int limit, + grn_obj *result) +{ + int n_entries = 0, e = offset + limit; + geo_entry *entries; + + if ((entries = GRN_MALLOC(sizeof(geo_entry) * (e + 1)))) { + int n, diff_bit; + double d_far; + geo_entry *ep; + grn_bool need_not_indexed_records; + grn_hash *indexed_records = NULL; + + n = grn_geo_table_sort_detect_far_point(ctx, table, index, pat, + entries, pc, e, accessorp, + base_point, + &d_far, &diff_bit); + if (diff_bit > 0) { + n = grn_geo_table_sort_collect_points(ctx, table, index, pat, + entries, n, e, accessorp, + base_point, d_far, diff_bit); + } + need_not_indexed_records = offset + limit > n; + if (need_not_indexed_records) { + indexed_records = grn_hash_create(ctx, NULL, sizeof(grn_id), 0, + GRN_OBJ_TABLE_HASH_KEY|GRN_HASH_TINY); + } + for (ep = entries + offset; + n_entries < limit && ep < entries + n; + n_entries++, ep++) { + grn_id *sorted_id; + if (!grn_array_add(ctx, (grn_array *)result, (void **)&sorted_id)) { + if (indexed_records) { + grn_hash_close(ctx, indexed_records); + indexed_records = NULL; + } + break; + } + *sorted_id = ep->id; + if (indexed_records) { + grn_hash_add(ctx, indexed_records, &(ep->id), sizeof(grn_id), + NULL, NULL); + } + } + GRN_FREE(entries); + if (indexed_records) { + GRN_TABLE_EACH(ctx, table, GRN_ID_NIL, GRN_ID_MAX, id, NULL, NULL, NULL, { + if (!grn_hash_get(ctx, indexed_records, &id, sizeof(grn_id), NULL)) { + grn_id *sorted_id; + if (grn_array_add(ctx, (grn_array *)result, (void **)&sorted_id)) { + *sorted_id = id; + } + n_entries++; + if (n_entries == limit) { + break; + } + }; + }); + grn_hash_close(ctx, indexed_records); + } + } + + return n_entries; +} + +int +grn_geo_table_sort(grn_ctx *ctx, grn_obj *table, int offset, int limit, + grn_obj *result, grn_obj *column, grn_obj *geo_point) +{ + grn_obj *index; + int i = 0; + + GRN_API_ENTER; + + if (offset < 0 || limit < 0) { + unsigned int size; + grn_rc rc; + size = grn_table_size(ctx, table); + rc = grn_normalize_offset_and_limit(ctx, size, &offset, &limit); + if (rc != GRN_SUCCESS) { + ERR(rc, + "[sort][geo] failed to normalize offset and limit: " + "offset:%d limit:%d table-size:%u", + offset, limit, size); + GRN_API_RETURN(i); + } + } + + if ((index = find_geo_sort_index(ctx, column))) { + grn_id tid; + grn_pat *pat = (grn_pat *)grn_ctx_at(ctx, index->header.domain); + grn_id domain; + grn_pat_cursor *pc; + if (!pat) { + char index_name[GRN_TABLE_MAX_KEY_SIZE]; + int index_name_size; + char lexicon_name[GRN_TABLE_MAX_KEY_SIZE]; + int lexicon_name_size; + index_name_size = grn_obj_name(ctx, + index, + index_name, + GRN_TABLE_MAX_KEY_SIZE); + lexicon_name_size = grn_table_get_key(ctx, + grn_ctx_db(ctx), + index->header.domain, + lexicon_name, + GRN_TABLE_MAX_KEY_SIZE); + ERR(GRN_OBJECT_CORRUPT, + "[sort][geo] lexicon is broken: <%.*s>: <%.*s>(%d)", + index_name_size, index_name, + lexicon_name_size, lexicon_name, + index->header.domain); + GRN_API_RETURN(i); + } + domain = pat->obj.header.domain; + pc = grn_pat_cursor_open(ctx, pat, NULL, 0, + GRN_BULK_HEAD(geo_point), + GRN_BULK_VSIZE(geo_point), + 0, -1, GRN_CURSOR_PREFIX); + if (pc) { + if (domain != GRN_DB_TOKYO_GEO_POINT && domain != GRN_DB_WGS84_GEO_POINT) { + int e = offset + limit; + while (i < e && (tid = grn_pat_cursor_next(ctx, pc))) { + grn_ii_cursor *ic = grn_ii_cursor_open(ctx, (grn_ii *)index, tid, 0, 0, 1, 0); + if (ic) { + grn_posting *posting; + while (i < e && (posting = grn_ii_cursor_next(ctx, ic))) { + if (offset <= i) { + grn_id *v; + if (!grn_array_add(ctx, (grn_array *)result, (void **)&v)) { break; } + *v = posting->rid; + } + i++; + } + grn_ii_cursor_close(ctx, ic); + } + } + } else { + grn_geo_point *base_point = (grn_geo_point *)GRN_BULK_HEAD(geo_point); + i = grn_geo_table_sort_by_distance(ctx, table, index, pat, + pc, + GRN_ACCESSORP(column), + base_point, + offset, limit, result); + } + grn_pat_cursor_close(ctx, pc); + } + } + GRN_API_RETURN(i); +} + +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 *table, grn_obj *index, + int nargs, grn_obj **args, + grn_obj *res, grn_operator op) +{ + grn_geo_approximate_type type = GRN_GEO_APPROXIMATE_RECTANGLE; + + if (!(nargs == 4 || nargs == 5)) { + ERR(GRN_INVALID_ARGUMENT, + "geo_in_circle(): requires 3 or 4 arguments but was <%d> arguments", + nargs - 1); + return ctx->rc; + } + + if (!index) { + grn_obj *point_column; + char column_name[GRN_TABLE_MAX_KEY_SIZE]; + int column_name_size; + point_column = args[1]; + column_name_size = grn_obj_name(ctx, point_column, + column_name, GRN_TABLE_MAX_KEY_SIZE); + ERR(GRN_FUNCTION_NOT_IMPLEMENTED, + "geo_in_circle(): index for <%.*s> is missing", + column_name_size, column_name); + return ctx->rc; + } + + if (nargs == 5) { + if (grn_geo_resolve_approximate_type(ctx, args[4], &type) != GRN_SUCCESS) { + return ctx->rc; + } + } + + { + grn_obj *center_point, *distance; + center_point = args[2]; + distance = args[3]; + grn_geo_select_in_circle(ctx, index, center_point, distance, type, res, op); + } + + return ctx->rc; +} + +static grn_geo_distance_raw_func +grn_geo_resolve_distance_raw_func (grn_ctx *ctx, + grn_geo_approximate_type approximate_type, + grn_id domain) +{ + grn_geo_distance_raw_func distance_raw_func = NULL; + + 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 : + break; + } + + return distance_raw_func; +} + +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 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); + if (!pat) { + char index_name[GRN_TABLE_MAX_KEY_SIZE]; + int index_name_size; + char lexicon_name[GRN_TABLE_MAX_KEY_SIZE]; + int lexicon_name_size; + index_name_size = grn_obj_name(ctx, + index, + index_name, + GRN_TABLE_MAX_KEY_SIZE); + lexicon_name_size = grn_table_get_key(ctx, + grn_ctx_db(ctx), + index->header.domain, + lexicon_name, + GRN_TABLE_MAX_KEY_SIZE); + ERR(GRN_OBJECT_CORRUPT, + "geo_in_circle(): lexicon is broken: <%.*s>: <%.*s>(%d)", + index_name_size, index_name, + lexicon_name_size, lexicon_name, + index->header.domain); + goto exit; + } + domain = pat->header.domain; + if (domain != GRN_DB_TOKYO_GEO_POINT && domain != GRN_DB_WGS84_GEO_POINT) { + char name[GRN_TABLE_MAX_KEY_SIZE]; + int name_size = 0; + grn_obj *domain_object; + domain_object = grn_ctx_at(ctx, domain); + if (domain_object) { + name_size = grn_obj_name(ctx, domain_object, name, GRN_TABLE_MAX_KEY_SIZE); + grn_obj_unlink(ctx, domain_object); + } else { + grn_strcpy(name, GRN_TABLE_MAX_KEY_SIZE, "(null)"); + name_size = strlen(name); + } + ERR(GRN_INVALID_ARGUMENT, + "geo_in_circle(): index table must be " + "TokyoGeoPoint or WGS84GeoPoint key type table: <%.*s>", + name_size, name); + goto exit; + } + + if (center_point->header.domain != domain) { + GRN_OBJ_INIT(¢er_point_, GRN_BULK, 0, domain); + if (grn_obj_cast(ctx, center_point, ¢er_point_, GRN_FALSE)) { goto exit; } + center_point = ¢er_point_; + } + center = GRN_GEO_POINT_VALUE_RAW(center_point); + center_longitude = GRN_GEO_INT2RAD(center->longitude); + center_latitude = GRN_GEO_INT2RAD(center->latitude); + + distance_raw_func = grn_geo_resolve_distance_raw_func(ctx, + approximate_type, + domain); + if (!distance_raw_func) { + ERR(GRN_INVALID_ARGUMENT, + "unknown approximate type: <%d>", approximate_type); + goto exit; + } + + switch (distance->header.domain) { + case GRN_DB_INT32 : + d = GRN_INT32_VALUE(distance); + on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d / (double)GRN_GEO_RADIUS); + on_circle.longitude = center->longitude; + break; + case GRN_DB_UINT32 : + d = GRN_UINT32_VALUE(distance); + on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d / (double)GRN_GEO_RADIUS); + on_circle.longitude = center->longitude; + break; + case GRN_DB_INT64 : + d = GRN_INT64_VALUE(distance); + on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d / (double)GRN_GEO_RADIUS); + on_circle.longitude = center->longitude; + break; + case GRN_DB_UINT64 : + d = GRN_UINT64_VALUE(distance); + on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d / (double)GRN_GEO_RADIUS); + on_circle.longitude = center->longitude; + break; + case GRN_DB_FLOAT : + d = GRN_FLOAT_VALUE(distance); + on_circle.latitude = center->latitude + GRN_GEO_RAD2INT(d / (double)GRN_GEO_RADIUS); + on_circle.longitude = center->longitude; + break; + case GRN_DB_SHORT_TEXT : + case GRN_DB_TEXT : + case GRN_DB_LONG_TEXT : + GRN_OBJ_INIT(&point_on_circle_, GRN_BULK, 0, domain); + if (grn_obj_cast(ctx, distance, &point_on_circle_, GRN_FALSE)) { goto exit; } + point_on_circle = &point_on_circle_; + /* fallthru */ + case GRN_DB_TOKYO_GEO_POINT : + case GRN_DB_WGS84_GEO_POINT : + if (!point_on_circle) { + if (domain != distance->header.domain) { /* todo */ goto exit; } + point_on_circle = distance; + } + GRN_GEO_POINT_VALUE(point_on_circle, + on_circle.latitude, on_circle.longitude); + d = distance_raw_func(ctx, center, &on_circle); + if (point_on_circle == &point_on_circle_) { + grn_obj_unlink(ctx, point_on_circle); + } + break; + default : + goto exit; + } + { + int n_meshes, diff_bit; + double d_far; + mesh_entry meshes[87]; + uint8_t geo_key1[sizeof(grn_geo_point)]; + uint8_t geo_key2[sizeof(grn_geo_point)]; + + d_far = grn_geo_distance_rectangle_raw(ctx, center, &on_circle); + grn_gton(geo_key1, center, sizeof(grn_geo_point)); + grn_gton(geo_key2, &on_circle, sizeof(grn_geo_point)); + diff_bit = compute_diff_bit(geo_key1, geo_key2); +#ifdef GEO_DEBUG + printf("center point: "); + grn_p_geo_point(ctx, center); + printf("point on circle: "); + grn_p_geo_point(ctx, &on_circle); + printf("diff: %d\n", diff_bit); +#endif + if ((diff_bit % 2) == 1) { + diff_bit--; + } + n_meshes = grn_geo_get_meshes_for_circle(ctx, center, + d_far, diff_bit, GRN_TRUE, + meshes); + while (n_meshes--) { + grn_table_cursor *tc; + tc = grn_table_cursor_open(ctx, pat, + &(meshes[n_meshes].key), + meshes[n_meshes].key_size, + NULL, 0, + 0, -1, + GRN_CURSOR_PREFIX|GRN_CURSOR_SIZE_BY_BIT); + inspect_mesh_entry(ctx, meshes, n_meshes); + if (tc) { + grn_id tid; + grn_geo_point point; + while ((tid = grn_table_cursor_next(ctx, tc))) { + double point_distance; + grn_table_get_key(ctx, pat, tid, &point, sizeof(grn_geo_point)); + 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); + } + } + grn_table_cursor_close(ctx, tc); + } + } + } +exit : + grn_ii_resolve_sel_and(ctx, (grn_hash *)res, op); + return ctx->rc; +} + +grn_rc +grn_selector_geo_in_rectangle(grn_ctx *ctx, grn_obj *table, grn_obj *index, + int nargs, grn_obj **args, + grn_obj *res, grn_operator op) +{ + if (nargs == 4) { + grn_obj *top_left_point, *bottom_right_point; + top_left_point = args[2]; + bottom_right_point = args[3]; + grn_geo_select_in_rectangle(ctx, index, + top_left_point, bottom_right_point, + res, op); + } else { + ERR(GRN_INVALID_ARGUMENT, + "geo_in_rectangle(): requires 3 arguments but was <%d> arguments", + nargs - 1); + } + return ctx->rc; +} + +static void +in_rectangle_data_fill(grn_ctx *ctx, grn_obj *index, + grn_obj *top_left_point, + grn_obj *bottom_right_point, + const char *process_name, + in_rectangle_data *data) +{ + grn_id domain; + const char *domain_name; + + data->pat = grn_ctx_at(ctx, index->header.domain); + if (!data->pat) { + char index_name[GRN_TABLE_MAX_KEY_SIZE]; + int index_name_size; + char lexicon_name[GRN_TABLE_MAX_KEY_SIZE]; + int lexicon_name_size; + index_name_size = grn_obj_name(ctx, + index, + index_name, + GRN_TABLE_MAX_KEY_SIZE); + lexicon_name_size = grn_table_get_key(ctx, + grn_ctx_db(ctx), + index->header.domain, + lexicon_name, + GRN_TABLE_MAX_KEY_SIZE); + ERR(GRN_OBJECT_CORRUPT, + "%s: lexicon lexicon is broken: <%.*s>: <%.*s>(%d)", + process_name, + index_name_size, index_name, + lexicon_name_size, lexicon_name, + index->header.domain); + return; + } + + domain = data->pat->header.domain; + if (domain != GRN_DB_TOKYO_GEO_POINT && domain != GRN_DB_WGS84_GEO_POINT) { + char name[GRN_TABLE_MAX_KEY_SIZE]; + int name_size = 0; + grn_obj *domain_object; + domain_object = grn_ctx_at(ctx, domain); + if (domain_object) { + name_size = grn_obj_name(ctx, domain_object, name, GRN_TABLE_MAX_KEY_SIZE); + grn_obj_unlink(ctx, domain_object); + } else { + grn_strcpy(name, GRN_TABLE_MAX_KEY_SIZE, "(null)"); + name_size = strlen(name); + } + ERR(GRN_INVALID_ARGUMENT, + "%s: index table must be " + "TokyoGeoPoint or WGS84GeoPoint key type table: <%.*s>", + process_name, name_size, name); + return; + } + + if (domain == GRN_DB_TOKYO_GEO_POINT) { + domain_name = "TokyoGeoPoint"; + } else { + domain_name = "WGS84GeoPoint"; + } + + if (top_left_point->header.domain != domain) { + grn_obj_reinit(ctx, &(data->top_left_point_buffer), domain, GRN_BULK); + if (grn_obj_cast(ctx, top_left_point, &(data->top_left_point_buffer), + GRN_FALSE)) { + ERR(GRN_INVALID_ARGUMENT, + "%s: failed to cast to %s: <%.*s>", + process_name, domain_name, + (int)GRN_TEXT_LEN(top_left_point), + GRN_TEXT_VALUE(top_left_point)); + return; + } + top_left_point = &(data->top_left_point_buffer); + } + data->top_left = GRN_GEO_POINT_VALUE_RAW(top_left_point); + + if (bottom_right_point->header.domain != domain) { + grn_obj_reinit(ctx, &(data->bottom_right_point_buffer), domain, GRN_BULK); + if (grn_obj_cast(ctx, bottom_right_point, &(data->bottom_right_point_buffer), + GRN_FALSE)) { + ERR(GRN_INVALID_ARGUMENT, + "%s: failed to cast to %s: <%.*s>", + process_name, domain_name, + (int)GRN_TEXT_LEN(bottom_right_point), + GRN_TEXT_VALUE(bottom_right_point)); + return; + } + bottom_right_point = &(data->bottom_right_point_buffer); + } + data->bottom_right = GRN_GEO_POINT_VALUE_RAW(bottom_right_point); +} + +static void +in_rectangle_data_validate(grn_ctx *ctx, + const char *process_name, + in_rectangle_data *data) +{ + grn_geo_point *top_left, *bottom_right; + + top_left = data->top_left; + bottom_right = data->bottom_right; + + if (top_left->latitude >= GRN_GEO_MAX_LATITUDE) { + ERR(GRN_INVALID_ARGUMENT, + "%s: top left point's latitude is too big: " + "<%d>(max:%d): (%d,%d) (%d,%d)", + process_name, + GRN_GEO_MAX_LATITUDE, top_left->latitude, + top_left->latitude, top_left->longitude, + bottom_right->latitude, bottom_right->longitude); + return; + } + + if (top_left->latitude <= GRN_GEO_MIN_LATITUDE) { + ERR(GRN_INVALID_ARGUMENT, + "%s: top left point's latitude is too small: " + "<%d>(min:%d): (%d,%d) (%d,%d)", + process_name, + GRN_GEO_MIN_LATITUDE, top_left->latitude, + top_left->latitude, top_left->longitude, + bottom_right->latitude, bottom_right->longitude); + return; + } + + if (top_left->longitude >= GRN_GEO_MAX_LONGITUDE) { + ERR(GRN_INVALID_ARGUMENT, + "%s: top left point's longitude is too big: " + "<%d>(max:%d): (%d,%d) (%d,%d)", + process_name, + GRN_GEO_MAX_LONGITUDE, top_left->longitude, + top_left->latitude, top_left->longitude, + bottom_right->latitude, bottom_right->longitude); + return; + } + + if (top_left->longitude <= GRN_GEO_MIN_LONGITUDE) { + ERR(GRN_INVALID_ARGUMENT, + "%s: top left point's longitude is too small: " + "<%d>(min:%d): (%d,%d) (%d,%d)", + process_name, + GRN_GEO_MIN_LONGITUDE, top_left->longitude, + top_left->latitude, top_left->longitude, + bottom_right->latitude, bottom_right->longitude); + return; + } + + if (bottom_right->latitude >= GRN_GEO_MAX_LATITUDE) { + ERR(GRN_INVALID_ARGUMENT, + "%s: bottom right point's latitude is too big: " + "<%d>(max:%d): (%d,%d) (%d,%d)", + process_name, + GRN_GEO_MAX_LATITUDE, bottom_right->latitude, + top_left->latitude, top_left->longitude, + bottom_right->latitude, bottom_right->longitude); + return; + } + + if (bottom_right->latitude <= GRN_GEO_MIN_LATITUDE) { + ERR(GRN_INVALID_ARGUMENT, + "%s: bottom right point's latitude is too small: " + "<%d>(min:%d): (%d,%d) (%d,%d)", + process_name, + GRN_GEO_MIN_LATITUDE, bottom_right->latitude, + top_left->latitude, top_left->longitude, + bottom_right->latitude, bottom_right->longitude); + return; + } + + if (bottom_right->longitude >= GRN_GEO_MAX_LONGITUDE) { + ERR(GRN_INVALID_ARGUMENT, + "%s: bottom right point's longitude is too big: " + "<%d>(max:%d): (%d,%d) (%d,%d)", + process_name, + GRN_GEO_MAX_LONGITUDE, bottom_right->longitude, + top_left->latitude, top_left->longitude, + bottom_right->latitude, bottom_right->longitude); + return; + } + + if (bottom_right->longitude <= GRN_GEO_MIN_LONGITUDE) { + ERR(GRN_INVALID_ARGUMENT, + "%s: bottom right point's longitude is too small: " + "<%d>(min:%d): (%d,%d) (%d,%d)", + process_name, + GRN_GEO_MIN_LONGITUDE, bottom_right->longitude, + top_left->latitude, top_left->longitude, + bottom_right->latitude, bottom_right->longitude); + return; + } +} + +static void +in_rectangle_area_data_compute(grn_ctx *ctx, + grn_geo_point *top_left, + grn_geo_point *bottom_right, + in_rectangle_area_data *data) +{ + int latitude_distance, longitude_distance; + int diff_bit; + grn_geo_point base; + grn_geo_point *geo_point_input; + uint8_t geo_key_input[sizeof(grn_geo_point)]; + uint8_t geo_key_base[sizeof(grn_geo_point)]; + uint8_t geo_key_top_left[sizeof(grn_geo_point)]; + uint8_t geo_key_bottom_right[sizeof(grn_geo_point)]; + + latitude_distance = top_left->latitude - bottom_right->latitude; + longitude_distance = bottom_right->longitude - top_left->longitude; + if (latitude_distance > longitude_distance) { + geo_point_input = bottom_right; + base.latitude = bottom_right->latitude; + base.longitude = bottom_right->longitude - longitude_distance; + } else { + geo_point_input = top_left; + base.latitude = top_left->latitude - latitude_distance; + base.longitude = top_left->longitude; + } + grn_gton(geo_key_input, geo_point_input, sizeof(grn_geo_point)); + grn_gton(geo_key_base, &base, sizeof(grn_geo_point)); + diff_bit = compute_diff_bit(geo_key_input, geo_key_base); + compute_min_and_max(&base, diff_bit, &(data->min), &(data->max)); + + grn_gton(geo_key_top_left, top_left, sizeof(grn_geo_point)); + grn_gton(geo_key_bottom_right, bottom_right, sizeof(grn_geo_point)); + data->rectangle_common_bit = + compute_diff_bit(geo_key_top_left, geo_key_bottom_right) - 1; + compute_min_and_max_key(geo_key_top_left, data->rectangle_common_bit + 1, + data->rectangle_common_key, NULL); + +#ifdef GEO_DEBUG + printf("base: "); + grn_p_geo_point(ctx, &base); + printf("min: "); + grn_p_geo_point(ctx, &(data->min)); + printf("max: "); + grn_p_geo_point(ctx, &(data->max)); + printf("top-left: "); + grn_p_geo_point(ctx, top_left); + printf("bottom-right: "); + grn_p_geo_point(ctx, bottom_right); + printf("rectangle-common-bit:%10d\n", data->rectangle_common_bit); + printf("distance(latitude): %10d\n", latitude_distance); + printf("distance(longitude): %10d\n", longitude_distance); +#endif +} + +static grn_rc +in_rectangle_data_prepare(grn_ctx *ctx, grn_obj *index, + grn_obj *top_left_point, + grn_obj *bottom_right_point, + const char *process_name, + in_rectangle_data *data) +{ + if (!index) { + ERR(GRN_FUNCTION_NOT_IMPLEMENTED, + "%s: index column is missing", process_name); + goto exit; + } + + in_rectangle_data_fill(ctx, index, top_left_point, bottom_right_point, + process_name, data); + if (ctx->rc != GRN_SUCCESS) { + goto exit; + } + + in_rectangle_data_validate(ctx, process_name, data); + if (ctx->rc != GRN_SUCCESS) { + goto exit; + } + +exit : + return ctx->rc; +} + +#define SAME_BIT_P(a, b, n_bit)\ + ((((uint8_t *)(a))[(n_bit) / 8] & (1 << (7 - ((n_bit) % 8)))) ==\ + (((uint8_t *)(b))[(n_bit) / 8] & (1 << (7 - ((n_bit) % 8))))) + +#define CURSOR_ENTRY_UPDATE_STATUS(entry, name, other_key) do {\ + if (SAME_BIT_P((entry)->key, (other_key), (entry)->target_bit)) {\ + (entry)->status_flags |= GRN_GEO_CURSOR_ENTRY_STATUS_ ## name;\ + } else {\ + (entry)->status_flags &= ~GRN_GEO_CURSOR_ENTRY_STATUS_ ## name;\ + }\ +} while (0) + +#define CURSOR_ENTRY_CHECK_STATUS(entry, name)\ + ((entry)->status_flags & GRN_GEO_CURSOR_ENTRY_STATUS_ ## name) +#define CURSOR_ENTRY_IS_INNER(entry)\ + (((entry)->status_flags &\ + (GRN_GEO_CURSOR_ENTRY_STATUS_LATITUDE_INNER |\ + GRN_GEO_CURSOR_ENTRY_STATUS_LONGITUDE_INNER)) ==\ + (GRN_GEO_CURSOR_ENTRY_STATUS_LATITUDE_INNER |\ + GRN_GEO_CURSOR_ENTRY_STATUS_LONGITUDE_INNER)) +#define CURSOR_ENTRY_INCLUDED_IN_LATITUDE_DIRECTION(entry)\ + ((entry)->status_flags &\ + (GRN_GEO_CURSOR_ENTRY_STATUS_LATITUDE_INNER |\ + GRN_GEO_CURSOR_ENTRY_STATUS_TOP_INCLUDED |\ + GRN_GEO_CURSOR_ENTRY_STATUS_BOTTOM_INCLUDED)) +#define CURSOR_ENTRY_INCLUDED_IN_LONGITUDE_DIRECTION(entry)\ + ((entry)->status_flags &\ + (GRN_GEO_CURSOR_ENTRY_STATUS_LONGITUDE_INNER |\ + GRN_GEO_CURSOR_ENTRY_STATUS_LEFT_INCLUDED |\ + GRN_GEO_CURSOR_ENTRY_STATUS_RIGHT_INCLUDED)) + +#define SET_N_BIT(a, n_bit)\ + (((uint8_t *)(a))[((n_bit) / 8)] ^= (1 << (7 - ((n_bit) % 8)))) + +#define N_BIT(a, n_bit)\ + ((((uint8_t *)(a))[((n_bit) / 8)] &\ + (1 << (7 - ((n_bit) % 8)))) >> (1 << (7 - ((n_bit) % 8)))) + +static grn_bool +extract_rectangle_in_area(grn_ctx *ctx, + grn_geo_area_type area_type, + const grn_geo_point *top_left, + const grn_geo_point *bottom_right, + grn_geo_point *area_top_left, + grn_geo_point *area_bottom_right) +{ + grn_bool out_of_area = GRN_FALSE; + grn_bool cover_all_areas = GRN_FALSE; + + if ((GRN_GEO_POINT_IN_NORTH_WEST(top_left) && + GRN_GEO_POINT_IN_SOUTH_EAST(bottom_right)) || + (GRN_GEO_POINT_IN_NORTH_EAST(top_left) && + GRN_GEO_POINT_IN_SOUTH_WEST(bottom_right))) { + cover_all_areas = GRN_TRUE; + } + + switch (area_type) { + case GRN_GEO_AREA_NORTH_EAST : + if (cover_all_areas || + GRN_GEO_POINT_IN_NORTH_EAST(top_left) || + GRN_GEO_POINT_IN_NORTH_EAST(bottom_right)) { + area_top_left->latitude = MAX(top_left->latitude, 0); + area_bottom_right->latitude = MAX(bottom_right->latitude, 0); + if (GRN_GEO_LONGITUDE_IS_WRAPPED(top_left, bottom_right)) { + area_top_left->longitude = top_left->longitude; + area_bottom_right->longitude = GRN_GEO_MAX_LONGITUDE; + } else { + area_top_left->longitude = MAX(top_left->longitude, 0); + area_bottom_right->longitude = MAX(bottom_right->longitude, 0); + } + } else { + out_of_area = GRN_TRUE; + } + break; + case GRN_GEO_AREA_NORTH_WEST : + if (cover_all_areas || + GRN_GEO_POINT_IN_NORTH_WEST(top_left) || + GRN_GEO_POINT_IN_NORTH_WEST(bottom_right)) { + area_top_left->latitude = MAX(top_left->latitude, 0); + area_bottom_right->latitude = MAX(bottom_right->latitude, 0); + if (GRN_GEO_LONGITUDE_IS_WRAPPED(top_left, bottom_right)) { + area_top_left->longitude = GRN_GEO_MIN_LONGITUDE; + area_bottom_right->longitude = bottom_right->longitude; + } else { + area_top_left->longitude = MIN(top_left->longitude, -1); + area_bottom_right->longitude = MIN(bottom_right->longitude, -1); + } + } else { + out_of_area = GRN_TRUE; + } + break; + case GRN_GEO_AREA_SOUTH_WEST : + if (cover_all_areas || + GRN_GEO_POINT_IN_SOUTH_WEST(top_left) || + GRN_GEO_POINT_IN_SOUTH_WEST(bottom_right)) { + area_top_left->latitude = MIN(top_left->latitude, -1); + area_bottom_right->latitude = MIN(bottom_right->latitude, -1); + if (GRN_GEO_LONGITUDE_IS_WRAPPED(top_left, bottom_right)) { + area_top_left->longitude = GRN_GEO_MIN_LONGITUDE; + area_bottom_right->longitude = bottom_right->longitude; + } else { + area_top_left->longitude = MIN(top_left->longitude, -1); + area_bottom_right->longitude = MIN(bottom_right->longitude, -1); + } + } else { + out_of_area = GRN_TRUE; + } + break; + case GRN_GEO_AREA_SOUTH_EAST : + if (cover_all_areas || + GRN_GEO_POINT_IN_SOUTH_EAST(top_left) || + GRN_GEO_POINT_IN_SOUTH_EAST(bottom_right)) { + area_top_left->latitude = MIN(top_left->latitude, -1); + area_bottom_right->latitude = MIN(bottom_right->latitude, -1); + if (GRN_GEO_LONGITUDE_IS_WRAPPED(top_left, bottom_right)) { + area_top_left->longitude = top_left->longitude; + area_bottom_right->longitude = GRN_GEO_MAX_LONGITUDE; + } else { + area_top_left->longitude = MAX(top_left->longitude, 0); + area_bottom_right->longitude = MAX(bottom_right->longitude, 0); + } + } else { + out_of_area = GRN_TRUE; + } + break; + default : + out_of_area = GRN_TRUE; + break; + } + + return out_of_area; +} + +static void +grn_geo_cursor_area_init(grn_ctx *ctx, + grn_geo_cursor_area *area, + grn_geo_area_type area_type, + const grn_geo_point *top_left, + const grn_geo_point *bottom_right) +{ + grn_geo_point area_top_left, area_bottom_right; + in_rectangle_area_data data; + grn_geo_cursor_entry *entry; + grn_bool out_of_area; + + out_of_area = extract_rectangle_in_area(ctx, + area_type, + top_left, + bottom_right, + &area_top_left, + &area_bottom_right); + if (out_of_area) { + area->current_entry = -1; + return; + } + + area->current_entry = 0; + grn_memcpy(&(area->top_left), &area_top_left, sizeof(grn_geo_point)); + grn_memcpy(&(area->bottom_right), &area_bottom_right, sizeof(grn_geo_point)); + grn_gton(area->top_left_key, &area_top_left, sizeof(grn_geo_point)); + grn_gton(area->bottom_right_key, &area_bottom_right, sizeof(grn_geo_point)); + + entry = &(area->entries[area->current_entry]); + in_rectangle_area_data_compute(ctx, + &area_top_left, + &area_bottom_right, + &data); + entry->target_bit = data.rectangle_common_bit; + grn_memcpy(entry->key, data.rectangle_common_key, sizeof(grn_geo_point)); + entry->status_flags = + GRN_GEO_CURSOR_ENTRY_STATUS_TOP_INCLUDED | + GRN_GEO_CURSOR_ENTRY_STATUS_BOTTOM_INCLUDED | + GRN_GEO_CURSOR_ENTRY_STATUS_LEFT_INCLUDED | + GRN_GEO_CURSOR_ENTRY_STATUS_RIGHT_INCLUDED; + if (data.min.latitude == area_bottom_right.latitude && + data.max.latitude == area_top_left.latitude) { + entry->status_flags |= GRN_GEO_CURSOR_ENTRY_STATUS_LATITUDE_INNER; + } + if (data.min.longitude == area_top_left.longitude && + data.max.longitude == area_bottom_right.longitude) { + entry->status_flags |= GRN_GEO_CURSOR_ENTRY_STATUS_LONGITUDE_INNER; + } +} + +grn_obj * +grn_geo_cursor_open_in_rectangle(grn_ctx *ctx, + grn_obj *index, + grn_obj *top_left_point, + grn_obj *bottom_right_point, + int offset, + int limit) +{ + grn_geo_cursor_in_rectangle *cursor = NULL; + in_rectangle_data data; + + GRN_API_ENTER; + GRN_VOID_INIT(&(data.top_left_point_buffer)); + GRN_VOID_INIT(&(data.bottom_right_point_buffer)); + if (in_rectangle_data_prepare(ctx, index, top_left_point, bottom_right_point, + "geo_in_rectangle()", &data)) { + goto exit; + } + + cursor = GRN_MALLOCN(grn_geo_cursor_in_rectangle, 1); + if (!cursor) { + ERR(GRN_NO_MEMORY_AVAILABLE, + "[geo][cursor][in-rectangle] failed to allocate memory for geo cursor"); + goto exit; + } + + cursor->pat = data.pat; + cursor->index = index; + grn_memcpy(&(cursor->top_left), data.top_left, sizeof(grn_geo_point)); + grn_memcpy(&(cursor->bottom_right), data.bottom_right, sizeof(grn_geo_point)); + cursor->pat_cursor = NULL; + cursor->ii_cursor = NULL; + cursor->offset = offset; + cursor->rest = limit; + + cursor->current_area = GRN_GEO_AREA_NORTH_EAST; + { + grn_geo_area_type area_type; + const grn_geo_point *top_left = &(cursor->top_left); + const grn_geo_point *bottom_right = &(cursor->bottom_right); + for (area_type = GRN_GEO_AREA_NORTH_EAST; + area_type < GRN_GEO_AREA_LAST; + area_type++) { + grn_geo_cursor_area_init(ctx, &(cursor->areas[area_type]), + area_type, top_left, bottom_right); + } + } + { + char minimum_reduce_bit_env[GRN_ENV_BUFFER_SIZE]; + cursor->minimum_reduce_bit = 0; + grn_getenv("GRN_GEO_IN_RECTANGLE_MINIMUM_REDUCE_BIT", + minimum_reduce_bit_env, + GRN_ENV_BUFFER_SIZE); + if (minimum_reduce_bit_env[0]) { + cursor->minimum_reduce_bit = atoi(minimum_reduce_bit_env); + } + if (cursor->minimum_reduce_bit < 1) { + cursor->minimum_reduce_bit = 1; + } + } + GRN_DB_OBJ_SET_TYPE(cursor, GRN_CURSOR_COLUMN_GEO_INDEX); + { + grn_obj *db; + grn_id id; + db = grn_ctx_db(ctx); + id = grn_obj_register(ctx, db, NULL, 0); + DB_OBJ(cursor)->header.domain = GRN_ID_NIL; + DB_OBJ(cursor)->range = GRN_ID_NIL; + grn_db_obj_init(ctx, db, id, DB_OBJ(cursor)); + } + +exit : + grn_obj_unlink(ctx, &(data.top_left_point_buffer)); + grn_obj_unlink(ctx, &(data.bottom_right_point_buffer)); + GRN_API_RETURN((grn_obj *)cursor); +} + +static inline grn_bool +grn_geo_cursor_entry_next_push(grn_ctx *ctx, + grn_geo_cursor_in_rectangle *cursor, + grn_geo_cursor_entry *entry) +{ + grn_geo_cursor_entry *next_entry; + grn_geo_point entry_base; + grn_table_cursor *pat_cursor; + grn_bool pushed = GRN_FALSE; + + grn_ntog((uint8_t*)(&entry_base), entry->key, sizeof(grn_geo_point)); + pat_cursor = grn_table_cursor_open(ctx, + cursor->pat, + &entry_base, + entry->target_bit + 1, + NULL, 0, + 0, -1, + GRN_CURSOR_PREFIX|GRN_CURSOR_SIZE_BY_BIT); + if (pat_cursor) { + if (grn_table_cursor_next(ctx, pat_cursor)) { + grn_geo_cursor_area *area; + area = &(cursor->areas[cursor->current_area]); + next_entry = &(area->entries[++area->current_entry]); + grn_memcpy(next_entry, entry, sizeof(grn_geo_cursor_entry)); + pushed = GRN_TRUE; + } + grn_table_cursor_close(ctx, pat_cursor); + } + + return pushed; +} + +static inline grn_bool +grn_geo_cursor_entry_next(grn_ctx *ctx, + grn_geo_cursor_in_rectangle *cursor, + grn_geo_cursor_entry *entry) +{ + uint8_t *top_left_key; + uint8_t *bottom_right_key; + int max_target_bit = GRN_GEO_KEY_MAX_BITS - cursor->minimum_reduce_bit; + grn_geo_cursor_area *area = NULL; + + while (cursor->current_area < GRN_GEO_AREA_LAST) { + area = &(cursor->areas[cursor->current_area]); + if (area->current_entry >= 0) { + break; + } + cursor->current_area++; + area = NULL; + } + + if (!area) { + return GRN_FALSE; + } + + top_left_key = area->top_left_key; + bottom_right_key = area->bottom_right_key; + grn_memcpy(entry, + &(area->entries[area->current_entry--]), + sizeof(grn_geo_cursor_entry)); + while (GRN_TRUE) { + grn_geo_cursor_entry next_entry0, next_entry1; + grn_bool pushed = GRN_FALSE; + + /* + top_left_key: tl + bottom_right_key: br + + e.g.: top_left_key is at the top left sub mesh and + bottom_right_key is at the bottom right sub mesh. + top_left_key is also at the top left - bottom right + sub-sub mesh and + bottom_right_key is at the bottom right - bottom left + sub-sub mesh. + + ^latitude +----+----+----+----+ + | 1 |1010|1011|1110|1111| + | | | | | | + | 1 +----+----+----+----+ + \/ 0 |1000|1001|1100|1101| + | | tl | | | + +----+----+----+----+ + 1 |0010|0011|0110|0111| + | | | | | + 0 +----+----+----+----+ + 0 |0000|0001|0100|0101| + | | | br | | + +----+----+----+----+ + 0 1 0 1 + |-------| |-------| + 0 1 + <------> + longitude + + entry.target_bit + 1 -> next_entry0 + entry.target_bit + 1 and entry.key ^ (entry.target_bit + 1) in bit + -> next_entry1 + + entry: represents the biggest mesh. + (1010, 1011, 1110, 1111, + 1000, 1001, 1100, 1101, + 0010, 0011, 0110, 0111, + 0000, 0001, 0100, 0101) + next_entry0: represents bottom sub-mesh. + (0010, 0011, 0110, 0111, + 0000, 0001, 0100, 0101) + next_entry1: represents top sub-mesh. + (1010, 1011, 1110, 1111, + 1000, 1001, 1100, 1101) + + entry->status_flags = TOP_INCLUDED | + BOTTOM_INCLUDED | + LEFT_INCLUDED | + RIGHT_INCLUDED + next_entry0->status_flags = BOTTOM_INCLUDED | + LEFT_INCLUDED | + RIGHT_INCLUDED + next_entry1->status_flags = TOP_INCLUDED | + LEFT_INCLUDED | + RIGHT_INCLUDED + + Both next_entry1 and next_entry0 are pushed to the stack in cursor. + */ + +#ifdef GEO_DEBUG + inspect_cursor_entry(ctx, entry); +#endif + + if (entry->target_bit >= max_target_bit) { +#ifdef GEO_DEBUG + printf("%d: force stopping to reduce a mesh\n", entry->target_bit); +#endif + break; + } + + if (CURSOR_ENTRY_IS_INNER(entry)) { +#ifdef GEO_DEBUG + printf("%d: inner entries\n", entry->target_bit); +#endif + break; + } + + grn_memcpy(&next_entry0, entry, sizeof(grn_geo_cursor_entry)); + next_entry0.target_bit++; + grn_memcpy(&next_entry1, entry, sizeof(grn_geo_cursor_entry)); + next_entry1.target_bit++; + SET_N_BIT(next_entry1.key, next_entry1.target_bit); + +#ifdef GEO_DEBUG + inspect_cursor_entry_targets(ctx, entry, top_left_key, bottom_right_key, + &next_entry0, &next_entry1); +#endif + + if ((entry->target_bit + 1) % 2 == 0) { + if (CURSOR_ENTRY_CHECK_STATUS(entry, TOP_INCLUDED)) { + CURSOR_ENTRY_UPDATE_STATUS(&next_entry0, TOP_INCLUDED, top_left_key); + CURSOR_ENTRY_UPDATE_STATUS(&next_entry1, TOP_INCLUDED, top_left_key); + } + if (CURSOR_ENTRY_CHECK_STATUS(entry, BOTTOM_INCLUDED)) { + CURSOR_ENTRY_UPDATE_STATUS(&next_entry0, BOTTOM_INCLUDED, + bottom_right_key); + CURSOR_ENTRY_UPDATE_STATUS(&next_entry1, BOTTOM_INCLUDED, + bottom_right_key); + } + + if (CURSOR_ENTRY_CHECK_STATUS(entry, TOP_INCLUDED) && + !CURSOR_ENTRY_CHECK_STATUS(entry, BOTTOM_INCLUDED) && + CURSOR_ENTRY_CHECK_STATUS(&next_entry1, TOP_INCLUDED)) { + next_entry0.status_flags |= GRN_GEO_CURSOR_ENTRY_STATUS_LATITUDE_INNER; + } else if (!CURSOR_ENTRY_CHECK_STATUS(entry, TOP_INCLUDED) && + CURSOR_ENTRY_CHECK_STATUS(entry, BOTTOM_INCLUDED) && + CURSOR_ENTRY_CHECK_STATUS(&next_entry0, BOTTOM_INCLUDED)) { + next_entry1.status_flags |= GRN_GEO_CURSOR_ENTRY_STATUS_LATITUDE_INNER; + } + + if (CURSOR_ENTRY_INCLUDED_IN_LATITUDE_DIRECTION(&next_entry1)) { + if (grn_geo_cursor_entry_next_push(ctx, cursor, &next_entry1)) { + pushed = GRN_TRUE; +#ifdef GEO_DEBUG + printf("%d: latitude: push 1\n", next_entry1.target_bit); +#endif + } + } + if (CURSOR_ENTRY_INCLUDED_IN_LATITUDE_DIRECTION(&next_entry0)) { + if (grn_geo_cursor_entry_next_push(ctx, cursor, &next_entry0)) { + pushed = GRN_TRUE; +#ifdef GEO_DEBUG + printf("%d: latitude: push 0\n", next_entry0.target_bit); +#endif + } + } + } else { + if (CURSOR_ENTRY_CHECK_STATUS(entry, RIGHT_INCLUDED)) { + CURSOR_ENTRY_UPDATE_STATUS(&next_entry0, RIGHT_INCLUDED, + bottom_right_key); + CURSOR_ENTRY_UPDATE_STATUS(&next_entry1, RIGHT_INCLUDED, + bottom_right_key); + } + if (CURSOR_ENTRY_CHECK_STATUS(entry, LEFT_INCLUDED)) { + CURSOR_ENTRY_UPDATE_STATUS(&next_entry0, LEFT_INCLUDED, top_left_key); + CURSOR_ENTRY_UPDATE_STATUS(&next_entry1, LEFT_INCLUDED, top_left_key); + } + + if (CURSOR_ENTRY_CHECK_STATUS(entry, LEFT_INCLUDED) && + !CURSOR_ENTRY_CHECK_STATUS(entry, RIGHT_INCLUDED) && + CURSOR_ENTRY_CHECK_STATUS(&next_entry0, LEFT_INCLUDED)) { + next_entry1.status_flags |= GRN_GEO_CURSOR_ENTRY_STATUS_LONGITUDE_INNER; + } else if (!CURSOR_ENTRY_CHECK_STATUS(entry, LEFT_INCLUDED) && + CURSOR_ENTRY_CHECK_STATUS(entry, RIGHT_INCLUDED) && + CURSOR_ENTRY_CHECK_STATUS(&next_entry1, RIGHT_INCLUDED)) { + next_entry0.status_flags |= GRN_GEO_CURSOR_ENTRY_STATUS_LONGITUDE_INNER; + } + + if (CURSOR_ENTRY_INCLUDED_IN_LONGITUDE_DIRECTION(&next_entry1)) { + if (grn_geo_cursor_entry_next_push(ctx, cursor, &next_entry1)) { + pushed = GRN_TRUE; +#ifdef GEO_DEBUG + printf("%d: longitude: push 1\n", next_entry1.target_bit); +#endif + } + } + if (CURSOR_ENTRY_INCLUDED_IN_LONGITUDE_DIRECTION(&next_entry0)) { + if (grn_geo_cursor_entry_next_push(ctx, cursor, &next_entry0)) { + pushed = GRN_TRUE; +#ifdef GEO_DEBUG + printf("%d: longitude: push 0\n", next_entry0.target_bit); +#endif + } + } + } + + if (pushed) { +#ifdef GEO_DEBUG + int i; + + printf("%d: pushed\n", entry->target_bit); + printf("stack:\n"); + for (i = area->current_entry; i >= 0; i--) { + grn_geo_cursor_entry *stack_entry; + stack_entry = &(area->entries[i]); + printf("%2d: ", i); + inspect_key(ctx, stack_entry->key); + printf(" "); + print_key_mark(ctx, stack_entry->target_bit); + } +#endif + grn_memcpy(entry, + &(area->entries[area->current_entry--]), + sizeof(grn_geo_cursor_entry)); +#ifdef GEO_DEBUG + printf("%d: pop entry\n", entry->target_bit); +#endif + } else { + break; + } + } + +#ifdef GEO_DEBUG + printf("found:\n"); + inspect_cursor_entry(ctx, entry); +#endif + + return GRN_TRUE; +} + +typedef grn_bool (*grn_geo_cursor_callback)(grn_ctx *ctx, grn_posting *posting, void *user_data); + +static void +grn_geo_cursor_each(grn_ctx *ctx, grn_obj *geo_cursor, + grn_geo_cursor_callback callback, void *user_data) +{ + grn_geo_cursor_in_rectangle *cursor; + grn_obj *pat; + grn_table_cursor *pat_cursor; + grn_ii *ii; + grn_ii_cursor *ii_cursor; + grn_posting *posting = NULL; + grn_geo_point *current, *top_left, *bottom_right; + grn_id index_id; + + cursor = (grn_geo_cursor_in_rectangle *)geo_cursor; + if (cursor->rest == 0) { + return; + } + + pat = cursor->pat; + pat_cursor = cursor->pat_cursor; + ii = (grn_ii *)(cursor->index); + ii_cursor = cursor->ii_cursor; + current = &(cursor->current); + top_left = &(cursor->top_left); + bottom_right = &(cursor->bottom_right); + + while (GRN_TRUE) { + if (!pat_cursor) { + grn_geo_cursor_entry entry; + grn_geo_point entry_base; + if (!grn_geo_cursor_entry_next(ctx, cursor, &entry)) { + cursor->rest = 0; + return; + } + grn_ntog((uint8_t*)(&entry_base), entry.key, sizeof(grn_geo_point)); + if (!(cursor->pat_cursor = pat_cursor = + grn_table_cursor_open(ctx, + pat, + &entry_base, + entry.target_bit + 1, + NULL, 0, + 0, -1, + GRN_CURSOR_PREFIX|GRN_CURSOR_SIZE_BY_BIT))) { + cursor->rest = 0; + return; + } +#ifdef GEO_DEBUG + inspect_mesh(ctx, &entry_base, entry.target_bit, 0); +#endif + } + + while (ii_cursor || (index_id = grn_table_cursor_next(ctx, pat_cursor))) { + if (!ii_cursor) { + grn_table_get_key(ctx, pat, index_id, current, sizeof(grn_geo_point)); + if (grn_geo_in_rectangle_raw(ctx, current, top_left, bottom_right)) { + inspect_tid(ctx, index_id, current, 0); + if (!(cursor->ii_cursor = ii_cursor = + grn_ii_cursor_open(ctx, + ii, + index_id, + GRN_ID_NIL, + GRN_ID_MAX, + ii->n_elements, + 0))) { + continue; + } + } else { + continue; + } + } + + while ((posting = grn_ii_cursor_next(ctx, ii_cursor))) { + if (cursor->offset == 0) { + grn_bool keep_each; + keep_each = callback(ctx, posting, user_data); + if (cursor->rest > 0) { + if (--(cursor->rest) == 0) { + keep_each = GRN_FALSE; + } + } + if (!keep_each) { + return; + } + } else { + cursor->offset--; + } + } + grn_ii_cursor_close(ctx, ii_cursor); + cursor->ii_cursor = ii_cursor = NULL; + } + grn_table_cursor_close(ctx, pat_cursor); + cursor->pat_cursor = pat_cursor = NULL; + } +} + +static grn_bool +grn_geo_cursor_next_callback(grn_ctx *ctx, grn_posting *posting, + void *user_data) +{ + grn_posting **return_posting = user_data; + *return_posting = posting; + return GRN_FALSE; +} + +grn_posting * +grn_geo_cursor_next(grn_ctx *ctx, grn_obj *geo_cursor) +{ + grn_posting *posting = NULL; + grn_geo_cursor_each(ctx, geo_cursor, grn_geo_cursor_next_callback, &posting); + return (grn_posting *)posting; +} + +grn_rc +grn_geo_cursor_close(grn_ctx *ctx, grn_obj *geo_cursor) +{ + grn_geo_cursor_in_rectangle *cursor; + + if (!geo_cursor) { return GRN_INVALID_ARGUMENT; } + + cursor = (grn_geo_cursor_in_rectangle *)geo_cursor; + if (cursor->pat) { grn_obj_unlink(ctx, cursor->pat); } + if (cursor->index) { grn_obj_unlink(ctx, cursor->index); } + if (cursor->pat_cursor) { grn_table_cursor_close(ctx, cursor->pat_cursor); } + if (cursor->ii_cursor) { grn_ii_cursor_close(ctx, cursor->ii_cursor); } + GRN_FREE(cursor); + + return GRN_SUCCESS; +} + +typedef struct { + grn_hash *res; + grn_operator op; +} grn_geo_select_in_rectangle_data; + +static grn_bool +grn_geo_select_in_rectangle_callback(grn_ctx *ctx, grn_posting *posting, + void *user_data) +{ + grn_geo_select_in_rectangle_data *data = user_data; + grn_ii_posting_add(ctx, posting, data->res, data->op); + return GRN_TRUE; +} + +grn_rc +grn_geo_select_in_rectangle(grn_ctx *ctx, grn_obj *index, + grn_obj *top_left_point, + grn_obj *bottom_right_point, + grn_obj *res, grn_operator op) +{ + grn_obj *cursor; + + cursor = grn_geo_cursor_open_in_rectangle(ctx, index, + top_left_point, bottom_right_point, + 0, -1); + if (cursor) { + grn_geo_select_in_rectangle_data data; + data.res = (grn_hash *)res; + data.op = op; + grn_geo_cursor_each(ctx, cursor, grn_geo_select_in_rectangle_callback, + &data); + grn_obj_unlink(ctx, cursor); + grn_ii_resolve_sel_and(ctx, (grn_hash *)res, op); + } + + return ctx->rc; +} + +static grn_rc +geo_point_get(grn_ctx *ctx, grn_obj *pat, int flags, grn_geo_point *geo_point) +{ + grn_rc rc = GRN_SUCCESS; + grn_id id; + grn_table_cursor *cursor = NULL; + + cursor = grn_table_cursor_open(ctx, pat, + NULL, 0, + NULL, 0, + 0, 1, + GRN_CURSOR_BY_KEY | flags); + if (!cursor) { + rc = ctx->rc; + goto exit; + } + + id = grn_table_cursor_next(ctx, cursor); + if (id == GRN_ID_NIL) { + rc = GRN_END_OF_DATA; + } else { + void *key; + int key_size; + key_size = grn_table_cursor_get_key(ctx, cursor, &key); + grn_memcpy(geo_point, key, key_size); + } + +exit: + if (cursor) { + grn_table_cursor_close(ctx, cursor); + } + return rc; +} + +uint32_t +grn_geo_estimate_size_in_rectangle(grn_ctx *ctx, + grn_obj *index, + grn_obj *top_left_point, + grn_obj *bottom_right_point) +{ + uint32_t n = 0; + int total_records; + grn_rc rc; + in_rectangle_data data; + + GRN_VOID_INIT(&(data.top_left_point_buffer)); + GRN_VOID_INIT(&(data.bottom_right_point_buffer)); + if (in_rectangle_data_prepare(ctx, index, top_left_point, bottom_right_point, + "grn_geo_estimate_in_rectangle()", &data)) { + goto exit; + } + + total_records = grn_table_size(ctx, data.pat); + if (total_records > 0) { + grn_geo_point min, max; + int select_latitude_distance, select_longitude_distance; + int total_latitude_distance, total_longitude_distance; + double select_ratio; + double estimated_n_records; + in_rectangle_area_data area_data; + + rc = geo_point_get(ctx, data.pat, GRN_CURSOR_ASCENDING, &min); + if (!rc) { + rc = geo_point_get(ctx, data.pat, GRN_CURSOR_DESCENDING, &max); + } + if (rc) { + if (rc == GRN_END_OF_DATA) { + n = total_records; + rc = GRN_SUCCESS; + } + goto exit; + } + + in_rectangle_area_data_compute(ctx, + data.top_left, + data.bottom_right, + &area_data); + select_latitude_distance = + abs(area_data.max.latitude - area_data.min.latitude); + select_longitude_distance = + abs(area_data.max.longitude - area_data.min.longitude); + total_latitude_distance = abs(max.latitude - min.latitude); + total_longitude_distance = abs(max.longitude - min.longitude); + + select_ratio = 1.0; + if (select_latitude_distance < total_latitude_distance) { + select_ratio *= ((double)select_latitude_distance / + (double)total_latitude_distance); + } + if (select_longitude_distance < total_longitude_distance) { + select_ratio *= ((double)select_longitude_distance / + (double)total_longitude_distance); + } + estimated_n_records = ceil(total_records * select_ratio); + n = (uint32_t)estimated_n_records; + } + +exit : + grn_obj_unlink(ctx, &(data.top_left_point_buffer)); + grn_obj_unlink(ctx, &(data.bottom_right_point_buffer)); + return n; +} + +int +grn_geo_estimate_in_rectangle(grn_ctx *ctx, + grn_obj *index, + grn_obj *top_left_point, + grn_obj *bottom_right_point) +{ + uint32_t size; + + size = grn_geo_estimate_size_in_rectangle(ctx, + index, + top_left_point, + bottom_right_point); + if (ctx->rc != GRN_SUCCESS) { + return -1; + } + + return size; +} + +grn_bool +grn_geo_in_circle(grn_ctx *ctx, grn_obj *point, grn_obj *center, + grn_obj *radius_or_point, + grn_geo_approximate_type approximate_type) +{ + grn_bool r = GRN_FALSE; + grn_obj center_, radius_or_point_; + grn_id domain = point->header.domain; + if (domain == GRN_DB_TOKYO_GEO_POINT || domain == GRN_DB_WGS84_GEO_POINT) { + grn_geo_distance_raw_func distance_raw_func; + double d; + if (center->header.domain != domain) { + GRN_OBJ_INIT(¢er_, GRN_BULK, 0, domain); + if (grn_obj_cast(ctx, center, ¢er_, GRN_FALSE)) { goto exit; } + center = ¢er_; + } + + distance_raw_func = grn_geo_resolve_distance_raw_func(ctx, + approximate_type, + domain); + if (!distance_raw_func) { + ERR(GRN_INVALID_ARGUMENT, + "unknown approximate type: <%d>", approximate_type); + goto exit; + } + d = distance_raw_func(ctx, + GRN_GEO_POINT_VALUE_RAW(point), + GRN_GEO_POINT_VALUE_RAW(center)); + switch (radius_or_point->header.domain) { + case GRN_DB_INT32 : + r = d <= GRN_INT32_VALUE(radius_or_point); + break; + case GRN_DB_UINT32 : + r = d <= GRN_UINT32_VALUE(radius_or_point); + break; + case GRN_DB_INT64 : + r = d <= GRN_INT64_VALUE(radius_or_point); + break; + case GRN_DB_UINT64 : + r = d <= GRN_UINT64_VALUE(radius_or_point); + break; + case GRN_DB_FLOAT : + r = d <= GRN_FLOAT_VALUE(radius_or_point); + break; + case GRN_DB_SHORT_TEXT : + case GRN_DB_TEXT : + case GRN_DB_LONG_TEXT : + GRN_OBJ_INIT(&radius_or_point_, GRN_BULK, 0, domain); + if (grn_obj_cast(ctx, radius_or_point, &radius_or_point_, GRN_FALSE)) { goto exit; } + radius_or_point = &radius_or_point_; + /* fallthru */ + case GRN_DB_TOKYO_GEO_POINT : + case GRN_DB_WGS84_GEO_POINT : + if (domain != radius_or_point->header.domain) { /* todo */ goto exit; } + r = d <= distance_raw_func(ctx, + GRN_GEO_POINT_VALUE_RAW(radius_or_point), + GRN_GEO_POINT_VALUE_RAW(center)); + break; + default : + goto exit; + } + } else { + /* todo */ + } +exit : + return r; +} + +grn_bool +grn_geo_in_rectangle_raw(grn_ctx *ctx, grn_geo_point *point, + grn_geo_point *top_left, grn_geo_point *bottom_right) +{ + if (point->latitude > top_left->latitude) { + return GRN_FALSE; + } + if (point->latitude < bottom_right->latitude) { + return GRN_FALSE; + } + + if (GRN_GEO_LONGITUDE_IS_WRAPPED(top_left, bottom_right)) { + if (point->longitude >= top_left->longitude) { + return GRN_TRUE; + } + if (point->longitude <= bottom_right->longitude) { + return GRN_TRUE; + } + return GRN_FALSE; + } else { + if (point->longitude < top_left->longitude) { + return GRN_FALSE; + } + if (point->longitude > bottom_right->longitude) { + return GRN_FALSE; + } + return GRN_TRUE; + } +} + +grn_bool +grn_geo_in_rectangle(grn_ctx *ctx, grn_obj *point, + grn_obj *top_left, grn_obj *bottom_right) +{ + grn_bool r = GRN_FALSE; + grn_obj top_left_, bottom_right_; + grn_id domain = point->header.domain; + if (domain == GRN_DB_TOKYO_GEO_POINT || domain == GRN_DB_WGS84_GEO_POINT) { + if (top_left->header.domain != domain) { + GRN_OBJ_INIT(&top_left_, GRN_BULK, 0, domain); + if (grn_obj_cast(ctx, top_left, &top_left_, GRN_FALSE)) { goto exit; } + top_left = &top_left_; + } + if (bottom_right->header.domain != domain) { + GRN_OBJ_INIT(&bottom_right_, GRN_BULK, 0, domain); + if (grn_obj_cast(ctx, bottom_right, &bottom_right_, GRN_FALSE)) { goto exit; } + bottom_right = &bottom_right_; + } + r = grn_geo_in_rectangle_raw(ctx, + GRN_GEO_POINT_VALUE_RAW(point), + GRN_GEO_POINT_VALUE_RAW(top_left), + GRN_GEO_POINT_VALUE_RAW(bottom_right)); + } else { + /* todo */ + } +exit : + return r; +} + +typedef enum { + LONGITUDE_SHORT, + LONGITUDE_LONG, +} distance_type; + +typedef enum { + QUADRANT_1ST, + QUADRANT_2ND, + QUADRANT_3RD, + QUADRANT_4TH, + QUADRANT_1ST_TO_2ND, + QUADRANT_1ST_TO_3RD, + QUADRANT_1ST_TO_4TH, + QUADRANT_2ND_TO_1ST, + QUADRANT_2ND_TO_3RD, + QUADRANT_2ND_TO_4TH, + QUADRANT_3RD_TO_1ST, + QUADRANT_3RD_TO_2ND, + QUADRANT_3RD_TO_4TH, + QUADRANT_4TH_TO_1ST, + QUADRANT_4TH_TO_2ND, + QUADRANT_4TH_TO_3RD, +} quadrant_type; + +static distance_type +geo_longitude_distance_type(int start_longitude, int end_longitude) +{ + int diff_longitude; + int east_to_west; + int west_to_east; + if (start_longitude >= 0) { + diff_longitude = abs(start_longitude - end_longitude); + } else { + diff_longitude = abs(end_longitude - start_longitude); + } + east_to_west = start_longitude > 0 && end_longitude < 0; + west_to_east = start_longitude < 0 && end_longitude > 0; + if (start_longitude != end_longitude && + (east_to_west || west_to_east) && + diff_longitude > 180 * GRN_GEO_RESOLUTION) { + return LONGITUDE_LONG; + } else { + return LONGITUDE_SHORT; + } +} + +static inline quadrant_type +geo_quadrant_type(grn_geo_point *point1, grn_geo_point *point2) +{ +#define QUADRANT_1ST_WITH_AXIS(point) \ + (point->longitude >= 0) && (point->latitude >= 0) +#define QUADRANT_2ND_WITH_AXIS(point) \ + (point->longitude <= 0) && (point->latitude >= 0) +#define QUADRANT_3RD_WITH_AXIS(point) \ + (point->longitude <= 0) && (point->latitude <= 0) +#define QUADRANT_4TH_WITH_AXIS(point) \ + (point->longitude >= 0) && (point->latitude <= 0) + + if (QUADRANT_1ST_WITH_AXIS(point1) && QUADRANT_1ST_WITH_AXIS(point2)) { + return QUADRANT_1ST; + } else if (QUADRANT_2ND_WITH_AXIS(point1) && QUADRANT_2ND_WITH_AXIS(point2)) { + return QUADRANT_2ND; + } else if (QUADRANT_3RD_WITH_AXIS(point1) && QUADRANT_3RD_WITH_AXIS(point2)) { + return QUADRANT_3RD; + } else if (QUADRANT_4TH_WITH_AXIS(point1) && QUADRANT_4TH_WITH_AXIS(point2)) { + return QUADRANT_4TH; + } else { + if (point1->longitude > 0 && point2->longitude < 0 && + point1->latitude >= 0 && point2->latitude >= 0) { + return QUADRANT_1ST_TO_2ND; + } else if (point1->longitude < 0 && point2->longitude > 0 && + point1->latitude >= 0 && point2->latitude >= 0) { + return QUADRANT_2ND_TO_1ST; + } else if (point1->longitude < 0 && point2->longitude > 0 && + point1->latitude <= 0 && point2->latitude <= 0) { + return QUADRANT_3RD_TO_4TH; + } else if (point1->longitude > 0 && point2->longitude < 0 && + point1->latitude <= 0 && point2->latitude <= 0) { + return QUADRANT_4TH_TO_3RD; + } else if (point1->longitude >= 0 && point2->longitude >= 0 && + point1->latitude > 0 && point2->latitude < 0) { + return QUADRANT_1ST_TO_4TH; + } else if (point1->longitude >= 0 && point2->longitude >= 0 && + point1->latitude < 0 && point2->latitude > 0) { + return QUADRANT_4TH_TO_1ST; + } else if (point1->longitude <= 0 && point2->longitude <= 0 && + point1->latitude > 0 && point2->latitude < 0) { + return QUADRANT_2ND_TO_3RD; + } else if (point1->longitude <= 0 && point2->longitude <= 0 && + point1->latitude < 0 && point2->latitude > 0) { + return QUADRANT_3RD_TO_2ND; + } else if (point1->longitude >= 0 && point2->longitude <= 0 && + point1->latitude > 0 && point2->latitude < 0) { + return QUADRANT_1ST_TO_3RD; + } else if (point1->longitude <= 0 && point2->longitude >= 0 && + point1->latitude < 0 && point2->latitude > 0) { + return QUADRANT_3RD_TO_1ST; + } else if (point1->longitude <= 0 && point2->longitude >= 0 && + point1->latitude > 0 && point2->latitude < 0) { + return QUADRANT_2ND_TO_4TH; + } else if (point1->longitude >= 0 && point2->longitude <= 0 && + point1->latitude < 0 && point2->latitude > 0) { + return QUADRANT_4TH_TO_2ND; + } else { + /* FIXME */ + return QUADRANT_1ST; + } + } +#undef QUADRANT_1ST_WITH_AXIS +#undef QUADRANT_2ND_WITH_AXIS +#undef QUADRANT_3RD_WITH_AXIS +#undef QUADRANT_4TH_WITH_AXIS +} + +static inline double +geo_distance_rectangle_square_root(double start_longitude, double start_latitude, + double end_longitude, double end_latitude) +{ + double diff_longitude; + double x, y; + + diff_longitude = end_longitude - start_longitude; + x = diff_longitude * cos((start_latitude + end_latitude) * 0.5); + y = end_latitude - start_latitude; + return sqrt((x * x) + (y * y)); +} + +static inline double +geo_distance_rectangle_short_dist_type(quadrant_type quad_type, + double lng1, double lat1, + double lng2, double lat2) +{ + double distance; + double longitude_delta, latitude_delta; + + if (quad_type == QUADRANT_1ST_TO_4TH || + quad_type == QUADRANT_4TH_TO_1ST || + quad_type == QUADRANT_2ND_TO_3RD || + quad_type == QUADRANT_3RD_TO_2ND) { + longitude_delta = lng2 - lng1; + if (longitude_delta > 0 || longitude_delta < 0) { + if (lat2 > lat1) { + distance = geo_distance_rectangle_square_root(lng1, + lat1, + lng2, + lat2) * GRN_GEO_RADIUS; + } else { + distance = geo_distance_rectangle_square_root(lng2, + lat2, + lng1, + lat1) * GRN_GEO_RADIUS; + } + } else { + latitude_delta = fabs(lat1) + fabs(lat2); + distance = sqrt(latitude_delta * latitude_delta) * GRN_GEO_RADIUS; + } + } else if (quad_type == QUADRANT_1ST_TO_3RD || + quad_type == QUADRANT_2ND_TO_4TH) { + distance = geo_distance_rectangle_square_root(lng1, + lat1, + lng2, + lat2) * GRN_GEO_RADIUS; + } else if (quad_type == QUADRANT_3RD_TO_1ST || + quad_type == QUADRANT_4TH_TO_2ND) { + distance = geo_distance_rectangle_square_root(lng2, + lat2, + lng1, + lat1) * GRN_GEO_RADIUS; + } else if (quad_type == QUADRANT_1ST_TO_2ND || + quad_type == QUADRANT_2ND_TO_1ST || + quad_type == QUADRANT_3RD_TO_4TH || + quad_type == QUADRANT_4TH_TO_3RD) { + if (lat2 > lat1) { + distance = geo_distance_rectangle_square_root(lng1, + lat1, + lng2, + lat2) * GRN_GEO_RADIUS; + } else if (lat2 < lat1) { + distance = geo_distance_rectangle_square_root(lng2, + lat2, + lng1, + lat1) * GRN_GEO_RADIUS; + } else { + longitude_delta = lng2 - lng1; + distance = longitude_delta * cos(lat1); + distance = sqrt(distance * distance) * GRN_GEO_RADIUS; + } + } else { + distance = geo_distance_rectangle_square_root(lng1, + lat1, + lng2, + lat2) * GRN_GEO_RADIUS; + } + return distance; +} + +static inline double +geo_distance_rectangle_long_dist_type(quadrant_type quad_type, + double lng1, double lat1, + double lng2, double lat2) +{ +#define M_2PI 6.28318530717958647692 + + double distance; + + if (quad_type == QUADRANT_1ST_TO_2ND || + quad_type == QUADRANT_4TH_TO_3RD) { + if (lat1 > lat2) { + distance = geo_distance_rectangle_square_root(lng2 + M_2PI, + lat2, + lng1, + lat1) * GRN_GEO_RADIUS; + } else { + distance = geo_distance_rectangle_square_root(lng1, + lat1, + lng2 + M_2PI, + lat2) * GRN_GEO_RADIUS; + } + } else if (quad_type == QUADRANT_2ND_TO_1ST || + quad_type == QUADRANT_3RD_TO_4TH) { + if (lat1 > lat2) { + distance = geo_distance_rectangle_square_root(lng2, + lat2, + lng1 + M_2PI, + lat1) * GRN_GEO_RADIUS; + } else { + distance = geo_distance_rectangle_square_root(lng1 + M_2PI, + lat1, + lng2, + lat2) * GRN_GEO_RADIUS; + } + } else if (quad_type == QUADRANT_1ST_TO_3RD) { + distance = geo_distance_rectangle_square_root(lng2 + M_2PI, + lat2, + lng1, + lat1) * GRN_GEO_RADIUS; + } else if (quad_type == QUADRANT_3RD_TO_1ST) { + distance = geo_distance_rectangle_square_root(lng1 + M_2PI, + lat1, + lng2, + lat2) * GRN_GEO_RADIUS; + } else if (quad_type == QUADRANT_2ND_TO_4TH) { + distance = geo_distance_rectangle_square_root(lng2, + lat2, + lng1 + M_2PI, + lat1) * GRN_GEO_RADIUS; + } else if (quad_type == QUADRANT_4TH_TO_2ND) { + distance = geo_distance_rectangle_square_root(lng1, + lat1, + lng2 + M_2PI, + lat2) * GRN_GEO_RADIUS; + } else { + if (lng1 > lng2) { + distance = geo_distance_rectangle_square_root(lng1, + lat1, + lng2 + M_2PI, + lat2) * GRN_GEO_RADIUS; + } else { + distance = geo_distance_rectangle_square_root(lng2, + lat2, + lng1 + M_2PI, + lat1) * GRN_GEO_RADIUS; + } + } + return distance; +#undef M_2PI +} + +double +grn_geo_distance_rectangle_raw(grn_ctx *ctx, + grn_geo_point *point1, grn_geo_point *point2) +{ + + double lng1, lat1, lng2, lat2, distance; + distance_type dist_type; + quadrant_type quad_type; + + lat1 = GRN_GEO_INT2RAD(point1->latitude); + lng1 = GRN_GEO_INT2RAD(point1->longitude); + lat2 = GRN_GEO_INT2RAD(point2->latitude); + lng2 = GRN_GEO_INT2RAD(point2->longitude); + quad_type = geo_quadrant_type(point1, point2); + if (quad_type <= QUADRANT_4TH) { + distance = geo_distance_rectangle_square_root(lng1, + lat1, + lng2, + lat2) * GRN_GEO_RADIUS; + } else { + dist_type = geo_longitude_distance_type(point1->longitude, + point2->longitude); + if (dist_type == LONGITUDE_SHORT) { + distance = geo_distance_rectangle_short_dist_type(quad_type, + lng1, lat1, + lng2, lat2); + } else { + distance = geo_distance_rectangle_long_dist_type(quad_type, + lng1, lat1, + lng2, lat2); + } + } + return distance; +} + +double +grn_geo_distance_sphere_raw(grn_ctx *ctx, + grn_geo_point *point1, grn_geo_point *point2) +{ + double lng1, lat1, lng2, lat2, x, y; + + lat1 = GRN_GEO_INT2RAD(point1->latitude); + lng1 = GRN_GEO_INT2RAD(point1->longitude); + lat2 = GRN_GEO_INT2RAD(point2->latitude); + lng2 = GRN_GEO_INT2RAD(point2->longitude); + x = sin(fabs(lng2 - lng1) * 0.5); + y = sin(fabs(lat2 - lat1) * 0.5); + return asin(sqrt((y * y) + cos(lat1) * cos(lat2) * x * x)) * 2 * GRN_GEO_RADIUS; +} + +double +grn_geo_distance_ellipsoid_raw(grn_ctx *ctx, + grn_geo_point *point1, grn_geo_point *point2, + int c1, int c2, double c3) +{ + double lng1, lat1, lng2, lat2, p, q, r, m, n, x, y; + + lat1 = GRN_GEO_INT2RAD(point1->latitude); + lng1 = GRN_GEO_INT2RAD(point1->longitude); + lat2 = GRN_GEO_INT2RAD(point2->latitude); + lng2 = GRN_GEO_INT2RAD(point2->longitude); + p = (lat1 + lat2) * 0.5; + q = (1 - c3 * sin(p) * sin(p)); + r = sqrt(q); + m = c1 / (q * r); + n = c2 / r; + x = n * cos(p) * fabs(lng1 - lng2); + y = m * fabs(lat1 - lat2); + return sqrt((x * x) + (y * y)); +} + +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) +{ + double d = 0.0; + + switch (type) { + case GRN_GEO_APPROXIMATE_RECTANGLE : + d = grn_geo_distance_rectangle(ctx, point1, point2); + break; + case GRN_GEO_APPROXIMATE_SPHERE : + d = grn_geo_distance_sphere(ctx, point1, point2); + break; + case GRN_GEO_APPROXIMATE_ELLIPSOID : + d = grn_geo_distance_ellipsoid(ctx, point1, point2); + break; + default : + ERR(GRN_INVALID_ARGUMENT, "unknown approximate type: <%d>", type); + break; + } + return d; +} + +double +grn_geo_distance_rectangle(grn_ctx *ctx, grn_obj *point1, grn_obj *point2) +{ + double d = 0; + grn_bool point1_initialized = GRN_FALSE; + grn_bool point2_initialized = GRN_FALSE; + grn_obj point1_, point2_; + grn_id domain1 = point1->header.domain; + grn_id domain2 = point2->header.domain; + if (domain1 == GRN_DB_TOKYO_GEO_POINT || domain1 == GRN_DB_WGS84_GEO_POINT) { + if (domain1 != domain2) { + GRN_OBJ_INIT(&point2_, GRN_BULK, 0, domain1); + point2_initialized = GRN_TRUE; + if (grn_obj_cast(ctx, point2, &point2_, GRN_FALSE)) { goto exit; } + point2 = &point2_; + } + } else if (domain2 == GRN_DB_TOKYO_GEO_POINT || + domain2 == GRN_DB_WGS84_GEO_POINT) { + GRN_OBJ_INIT(&point1_, GRN_BULK, 0, domain2); + point1_initialized = GRN_TRUE; + if (grn_obj_cast(ctx, point1, &point1_, GRN_FALSE)) { goto exit; } + point1 = &point1_; + } else if ((GRN_DB_SHORT_TEXT <= domain1 && domain1 <= GRN_DB_LONG_TEXT) && + (GRN_DB_SHORT_TEXT <= domain2 && domain2 <= GRN_DB_LONG_TEXT)) { + GRN_OBJ_INIT(&point1_, GRN_BULK, 0, GRN_DB_WGS84_GEO_POINT); + point1_initialized = GRN_TRUE; + if (grn_obj_cast(ctx, point1, &point1_, GRN_FALSE)) { goto exit; } + point1 = &point1_; + + GRN_OBJ_INIT(&point2_, GRN_BULK, 0, GRN_DB_WGS84_GEO_POINT); + point2_initialized = GRN_TRUE; + if (grn_obj_cast(ctx, point2, &point2_, GRN_FALSE)) { goto exit; } + point2 = &point2_; + } else { + goto exit; + } + d = grn_geo_distance_rectangle_raw(ctx, + GRN_GEO_POINT_VALUE_RAW(point1), + GRN_GEO_POINT_VALUE_RAW(point2)); +exit : + if (point1_initialized) { + GRN_OBJ_FIN(ctx, &point1_); + } + if (point2_initialized) { + GRN_OBJ_FIN(ctx, &point2_); + } + return d; +} + +double +grn_geo_distance_sphere(grn_ctx *ctx, grn_obj *point1, grn_obj *point2) +{ + double d = 0; + grn_bool point2_initialized = GRN_FALSE; + grn_obj point2_; + grn_id domain = point1->header.domain; + if (domain == GRN_DB_TOKYO_GEO_POINT || domain == GRN_DB_WGS84_GEO_POINT) { + if (point2->header.domain != domain) { + GRN_OBJ_INIT(&point2_, GRN_BULK, 0, domain); + point2_initialized = GRN_TRUE; + if (grn_obj_cast(ctx, point2, &point2_, GRN_FALSE)) { goto exit; } + point2 = &point2_; + } + d = grn_geo_distance_sphere_raw(ctx, + GRN_GEO_POINT_VALUE_RAW(point1), + GRN_GEO_POINT_VALUE_RAW(point2)); + } else { + /* todo */ + } +exit : + if (point2_initialized) { + GRN_OBJ_FIN(ctx, &point2_); + } + return d; +} + +double +grn_geo_distance_ellipsoid(grn_ctx *ctx, grn_obj *point1, grn_obj *point2) +{ + double d = 0; + grn_bool point2_initialized = GRN_FALSE; + grn_obj point2_; + grn_id domain = point1->header.domain; + if (domain == GRN_DB_TOKYO_GEO_POINT || domain == GRN_DB_WGS84_GEO_POINT) { + if (point2->header.domain != domain) { + GRN_OBJ_INIT(&point2_, GRN_BULK, 0, domain); + point2_initialized = GRN_TRUE; + if (grn_obj_cast(ctx, point2, &point2_, GRN_FALSE)) { goto exit; } + point2 = &point2_; + } + if (domain == GRN_DB_TOKYO_GEO_POINT) { + 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_wgs84(ctx, + GRN_GEO_POINT_VALUE_RAW(point1), + GRN_GEO_POINT_VALUE_RAW(point2)); + } + } else { + /* todo */ + } +exit : + if (point2_initialized) { + GRN_OBJ_FIN(ctx, &point2_); + } + return d; +} |