mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 10:56:12 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			2789 lines
		
	
	
	
		
			92 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2789 lines
		
	
	
	
		
			92 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* -*- 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)
 | |
| {
 | |
|   size_t i;
 | |
|   int 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;
 | |
| }
 | 
