├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README-CN.md ├── README.md ├── imgs └── tairgis_logo.png ├── src ├── CMakeLists.txt ├── redismodule.h ├── spatial.c ├── spatial.h ├── spatial │ ├── .gitignore │ ├── Makefile │ ├── bing.c │ ├── bing.h │ ├── geom.c │ ├── geom.h │ ├── geom_json.c │ ├── geom_levels.c │ ├── geom_polymap.c │ ├── geom_test.c │ ├── geoutil.c │ ├── geoutil.h │ ├── geoutil_test.c │ ├── grisu3.c │ ├── grisu3.h │ ├── hash.c │ ├── hash.h │ ├── json.c │ ├── json.h │ ├── poly.c │ ├── poly.h │ ├── poly_test.c │ ├── polyinside.c │ ├── polyinside_test.c │ ├── polyintersects.c │ ├── polyintersects_test.c │ ├── polyraycast.c │ ├── rtree.c │ ├── rtree.h │ ├── rtree_test.c │ ├── rtree_tmpl.c │ ├── test.c │ ├── test.h │ └── zmalloc.h ├── tairgis.c ├── tairgis.h ├── util.c └── util.h └── tests └── tairgis.tcl /.clang-format: -------------------------------------------------------------------------------- 1 | # We'll use defaults from the Google style, but with 4 columns indentation. 2 | BasedOnStyle: Google 3 | Language: Cpp 4 | IndentWidth: 4 5 | DerivePointerAlignment: false 6 | PointerAlignment: Right 7 | AccessModifierOffset: -4 8 | BreakBeforeBinaryOperators: All 9 | ColumnLimit: 0 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.o 3 | build/ 4 | cmake-build-debug/ 5 | .idea/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(tairgis_module) 3 | 4 | set(CMAKE_C_STANDARD 99) 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -W -Wall -g -ggdb -std=c99 -O2 -Wno-strict-aliasing -Wno-typedef-redefinition -Wno-sign-compare -Wno-unused-parameter") 6 | if (CMAKE_SYSTEM_NAME MATCHES "Linux") 7 | if (GCOV_MODE) 8 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage") 9 | endif() 10 | endif () 11 | 12 | add_subdirectory(src) 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 2 | 3 | COMPAT_MODULE = tairgis.so 4 | all: $(COMPAT_MODULE) 5 | 6 | COMPAT_DIR := build 7 | TOPDIR := $(shell pwd) 8 | 9 | ifeq ($(GCOV_MODE),TRUE) 10 | MODULE_FLAG = -DGCOV_MODE=TRUE 11 | endif 12 | 13 | $(COMPAT_MODULE): $(COMPAT_DIR)/tairgis.so 14 | cp $^ $@ 15 | 16 | $(COMPAT_DIR)/tairgis.so: 17 | rm -fr $(COMPAT_DIR) 18 | mkdir $(COMPAT_DIR) && cd $(COMPAT_DIR) && cmake .. ${MODULE_FLAG} 19 | $(MAKE) -C $(COMPAT_DIR) 20 | 21 | ifeq ($(uname_S),Linux) 22 | cp $(COMPAT_DIR)/src/libtairgis.so $(COMPAT_DIR)/tairgis.so 23 | else 24 | cp $(COMPAT_DIR)/src/libtairgis.dylib $(COMPAT_DIR)/tairgis.so 25 | endif 26 | 27 | clean: 28 | -rm -fr $(COMPAT_MODULE) $(COMPAT_DIR) 29 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | ## 简介 [English introduction](README.md) 6 | TairGis 是一个 Redis Module,支持点、线、面之间相交、包含、被包含关系的判断,具有以下特点: 7 | - 查询性能快 8 | - 使用`RTree`存储 9 | - 通过`GIS.SEARCH`可实现Redis`GEORADIUS`命令的功能 10 | 11 | ## 感谢 12 | 特别感谢 https://github.com/tidwall/redis-gis, TairGis 依赖 redis-gis,并修复了其部分问题。 13 | 14 | ## 快速开始 15 | ``` 16 | // 插入一个 POLYGON 17 | 127.0.0.1:6379> GIS.ADD hangzhou campus 'POLYGON ((120.028041 30.285179, 120.031203 30.28691, 120.037311 30.286239, 120.034185 30.280844))' 18 | (integer) 1 19 | // 获取 POLYGON 20 | 127.0.0.1:6379> GIS.GET hangzhou campus 21 | "POLYGON((120.028041 30.285179,120.031203 30.28691,120.037311 30.286239,120.034185 30.280844))" 22 | // 判断点 'POINT (120.031939 30.285179)' 和 POLYGON 是否包含 23 | 127.0.0.1:6379> GIS.CONTAINS hangzhou 'POINT (120.031939 30.285179)' 24 | 1) (integer) 1 25 | 2) 1) "campus" 26 | 2) "POLYGON((120.028041 30.285179,120.031203 30.28691,120.037311 30.286239,120.034185 30.280844))" 27 | // 判断线和 POLYGON 是否相交 28 | 127.0.0.1:6379> GIS.INTERSECTS hangzhou 'LINESTRING (120.029622 30.288952, 120.035488 30.280056)' 29 | 1) (integer) 1 30 | 2) 1) "campus" 31 | 2) "POLYGON((120.028041 30.285179,120.031203 30.28691,120.037311 30.286239,120.034185 30.280844))" 32 | 127.0.0.1:6379> 33 | ``` 34 | 35 | ## 编译及使用 36 | ``` 37 | make 38 | ``` 39 | 编译成功后,会在当前目录下产生`tairgis.so`文件。 40 | ``` 41 | ./redis-server --loadmodule /path/to/tairgis.so 42 | ``` 43 | 44 | ## 测试方法 45 | 修改 tests 目录下 tairgis.tcl 文件中的路径为:`set testmodule [file your_path/tairgis.so]` 46 | 47 | 将 tests 目录下 tairgis.tcl 拷贝至 redis 目录 tests 下 48 | ``` 49 | cp tests/tairgis.tcl your_redis_path/tests 50 | ``` 51 | 将 tairgis 添加到 redis 的 test_helper.tcl 的 all_tests 中 52 | ``` 53 | diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl 54 | index 1a5096937..d5a1ba40a 100644 55 | --- a/tests/test_helper.tcl 56 | +++ b/tests/test_helper.tcl 57 | @@ -13,6 +13,7 @@ source tests/support/test.tcl 58 | source tests/support/util.tcl 59 | 60 | set ::all_tests { 61 | + tairgis 62 | ``` 63 | 在redis根目录下运行 ./runtest --single tairgis 64 | 65 | ## 客户端 66 | | language | GitHub | 67 | |----------|---| 68 | | Java |https://github.com/alibaba/alibabacloud-tairjedis-sdk| 69 | | Python |https://github.com/alibaba/tair-py| 70 | | Go |https://github.com/alibaba/tair-go| 71 | | .Net |https://github.com/alibaba/AlibabaCloud.TairSDK| 72 | 73 | ## API 74 | 75 | ### GIS.ADD 76 | #### 语法及复杂度 77 | > GIS.ADD area polygonName polygonWkt [polygonName polygonWkt ...] 78 | > 时间复杂度:O(log n) 79 | 80 | #### 命令描述 81 | > 在area中添加指定名称的多边形(可添加多个),使用WKT(Well-known text)描述。WKT是一种文本标记语言,用于描述矢量几何对象、 82 | > 空间参照系统及空间参照系统之间的转换。 83 | 84 | #### 参数描述 85 | > area:一个几何概念。 86 | > polygonName:多边形的名称。 87 | > polygonWkt:多边形的描述信息,表示现实世界的经、纬度,使用WKT(Well-known text)描述,支持如下类型。 88 | > - POINT:描述一个点的WKT信息,例如'POINT (120.086631 30.138141)',表示该POINT位于经度120.086631,纬度30.138141。 89 | > - LINESTRING:描述一条线的WKT信息,由两个POINT组成,例如'LINESTRING (30 10, 40 40)'。 90 | > - POLYGON:描述一个多边形的WKT信息,由多个POINT组成,例如'POLYGON ((31 20, 29 20, 29 21, 31 31))'。 91 | > 说明:经度的取值范围为(-180,180), 纬度的取值范围为(-90,90)。不支持如下集合类型:MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、GEOMETRY和COLLECTION。 92 | 93 | #### 返回值 94 | > 执行成功:返回插入和更新成功的多边形数量。 95 | > 其它情况返回相应的异常信息。 96 | 97 | #### 示例 98 | ``` 99 | 127.0.0.1:6379> GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))' 100 | (integer) 1 101 | 127.0.0.1:6379> 102 | ``` 103 | 104 | ### GIS.GET 105 | #### 语法及复杂度 106 | > GIS.ADD area polygonName 107 | > 时间复杂度:O(1) 108 | 109 | #### 命令描述 110 | > 获取目标area中指定多边形的WKT信息。 111 | 112 | #### 参数描述 113 | > area:一个几何概念。 114 | > polygonName:多边形的名称。 115 | 116 | #### 返回值 117 | > 执行成功:WKT信息。 118 | > area或polygonName不存在:nil。 119 | > 其它情况返回相应的异常信息。 120 | 121 | #### 示例 122 | ``` 123 | 提前执行 GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'命令 124 | 125 | 127.0.0.1:6379> GIS.GET hangzhou campus 126 | "POLYGON((30 10,40 40,20 40,10 20,30 10))" 127 | 127.0.0.1:6379> 128 | ``` 129 | 130 | ### GIS.GETALL 131 | #### 语法及复杂度 132 | > GIS.ADD area [WITHOUTWKT] 133 | > 时间复杂度:O(n) 134 | 135 | #### 命令描述 136 | > 获取目标area中所有多边形的名称和WKT信息。如果设置了WITHOUTWKT选项,仅返回多边形的名称。 137 | 138 | #### 参数描述 139 | > area:一个几何概念。 140 | > WITHOUTWKT:用于控制是否返回多边形的WKT信息,如果加上该参数,则不返回多边形的WKT信息。 141 | 142 | #### 返回值 143 | > 执行成功:返回多边形名称和WKT信息,如果设置了WITHOUTWKT选项,仅返回多边形的名称。 144 | > area不存在:nil。 145 | > 其它情况返回相应的异常信息。 146 | 147 | #### 示例 148 | ``` 149 | 提前执行 GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'命令 150 | 151 | 127.0.0.1:6379> GIS.GETALL hangzhou 152 | 1) "campus" 153 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))" 154 | 127.0.0.1:6379> 155 | ``` 156 | 157 | ### GIS.DEL 158 | #### 语法及复杂度 159 | > GIS.DEL area polygonName 160 | > 时间复杂度:O(log n) 161 | 162 | #### 命令描述 163 | > 删除目标area中指定的多边形。 164 | 165 | #### 参数描述 166 | > area:一个几何概念。 167 | > polygonName:多边形的名称。 168 | 169 | #### 返回值 170 | > 执行成功:OK。 171 | > area或polygonName不存在:nil。 172 | > 其它情况返回相应的异常信息。 173 | 174 | #### 示例 175 | ``` 176 | 提前执行 GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'命令 177 | 178 | 127.0.0.1:6379> GIS.DEL hangzhou campus 179 | OK 180 | 127.0.0.1:6379> 181 | ``` 182 | 183 | ### GIS.CONTAINS 184 | #### 语法及复杂度 185 | > GIS.CONTAINS area polygonWkt [WITHOUTWKT] 186 | > 时间复杂度:最好O(log M n),最差O(log n) 187 | 188 | #### 命令描述 189 | > 判断指定的点、线或面是否包含在目标area的多边形中,若包含,则返回目标area中命中的多边形数量与多边形信息。 190 | 191 | #### 参数描述 192 | > area:一个几何概念。 193 | > polygonWkt:指定与目标area进行比较的多边形描述信息,使用WKT(Well-known text)描述,支持如下类型。 194 | > - POINT:描述一个点的WKT信息。 195 | > - LINESTRING:描述一条线的WKT信息。 196 | > - POLYGON:描述一个多边形的WKT信息。 197 | > 198 | > WITHOUTWKT:用于控制是否返回多边形的WKT信息,如果加上该参数,则不返回多边形的WKT信息。 199 | 200 | #### 返回值 201 | > 执行成功:命中的多边形数量与多边形信息。 202 | > area不存在:empty list or set。 203 | > 其它情况返回相应的异常信息。 204 | 205 | #### 示例 206 | ``` 207 | 提前执行 GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'命令 208 | 209 | 127.0.0.1:6379> GIS.CONTAINS hangzhou 'POINT (30 11)' 210 | 1) (integer) 1 211 | 2) 1) "campus" 212 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))" 213 | 127.0.0.1:6379> 214 | ``` 215 | 216 | ### GIS.WITHIN 217 | #### 语法及复杂度 218 | > GIS.WITHIN area polygonWkt [WITHOUTWKT] 219 | > 时间复杂度:最好O(log M n),最差O(log n) 220 | 221 | #### 命令描述 222 | > 判断目标area是否包含在指定的点、线或面中,若包含,则返回目标area中命中的多边形数量与多边形信息。 223 | 224 | #### 参数描述 225 | > area:一个几何概念。 226 | > polygonWkt:指定与目标area进行比较的多边形描述信息,使用WKT(Well-known text)描述,支持如下类型。 227 | > - POINT:描述一个点的WKT信息。 228 | > - LINESTRING:描述一条线的WKT信息。 229 | > - POLYGON:描述一个多边形的WKT信息。 230 | > 231 | > WITHOUTWKT:用于控制是否返回多边形的WKT信息,如果加上该参数,则不返回多边形的WKT信息。 232 | 233 | #### 返回值 234 | > 执行成功:命中的多边形数量与多边形信息。 235 | > area不存在:empty list or set。 236 | > 其它情况返回相应的异常信息。 237 | 238 | #### 示例 239 | ``` 240 | 提前执行 GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'命令 241 | 242 | 127.0.0.1:6379> GIS.WITHIN hangzhou 'POLYGON ((30 5, 50 50, 20 50, 5 20, 30 5))' 243 | 1) (integer) 1 244 | 2) 1) "campus" 245 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))" 246 | 127.0.0.1:6379> 247 | ``` 248 | 249 | ### GIS.INTERSECTS 250 | #### 语法及复杂度 251 | > GIS.INTERSECTS area polygonWkt [WITHOUTWKT] 252 | > 时间复杂度:最好O(log M n),最差O(log n) 253 | 254 | #### 命令描述 255 | > 判断指定的点、线或面与目标area的多边形是否相交,若相交,则返回目标area中与其相交的多边形数量与多边形信息。 256 | 257 | #### 参数描述 258 | > area:一个几何概念。 259 | > polygonWkt:指定与目标area进行比较的多边形描述信息,使用WKT(Well-known text)描述,支持如下类型。 260 | > - POINT:描述一个点的WKT信息。 261 | > - LINESTRING:描述一条线的WKT信息。 262 | > - POLYGON:描述一个多边形的WKT信息。 263 | > 264 | > WITHOUTWKT:用于控制是否返回多边形的WKT信息,如果加上该参数,则不返回多边形的WKT信息。 265 | 266 | #### 返回值 267 | > 执行成功:命中的多边形数量与多边形信息。 268 | > area不存在:empty list or set。 269 | > 其它情况返回相应的异常信息。 270 | 271 | #### 示例 272 | ``` 273 | 提前执行 GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'命令 274 | 275 | 127.0.0.1:6379> GIS.INTERSECTS hangzhou 'LINESTRING (30 10, 40 40)' 276 | 1) (integer) 1 277 | 2) 1) "campus" 278 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))" 279 | 127.0.0.1:6379> 280 | ``` 281 | 282 | ### GIS.SEARCH 283 | #### 语法及复杂度 284 | > GIS.SEARCH area [RADIUS longitude latitude distance m|km|ft|mi] 285 | > [MEMBER field distance m|km|ft|mi] 286 | > [GEOM geom] 287 | > [COUNT count] 288 | > [LIMIT limit] 289 | > [ASC|DESC] 290 | > [WITHDIST] 291 | > [WITHOUTWKT] 292 | > 时间复杂度:最好O(log M n),最差O(log n) 293 | 294 | #### 命令描述 295 | > 在指定经、纬度及半径距离范围内,查找目标area中的点。 296 | 297 | #### 参数描述 298 | > area:一个几何概念。 299 | > RADIUS:传入经度(longitude)、纬度(latitude)、半径距离(distance)和半径单位(m表示米、km表示千米、ft表示英尺、mi表示英里)进行搜索,例如RADIUS 15 37 200 km。 300 | > MEMBER:选择当前area中已存在的POINT作为搜索原点,并指定半径进行搜索,取值顺序为多边形名称(field)、半径(distance)、半径单位(m表示米、km表示千米、ft表示英尺、mi表示英里),例如MEMBER Agrigento 100 km。 301 | > GEOM:按照WKT的格式设置搜索范围,可以是任意多边形,例如GEOM 'POLYGON((10 30,20 30,20 40,10 40))'。 302 | > COUNT:用于限定返回的个数,例如COUNT 3。 303 | > LIMIT:Limit 与 Count 的区别是 Limit 是在搜索过程完成,只要搜索到 limit 个元素,就停止搜索(并不一定是最近的范围);但 Count 是搜索完所有元素并排序之后再进行过滤。 304 | > ASC|DESC:用于控制返回信息按照距离排序,ASC表示根据中心位置,由近到远排序;DESC表示由远到近排序。 305 | > WITHDIST:用于控制是否返回目标点与搜索原点的距离。 306 | > WITHOUTWKT:用于控制是否返回多边形的WKT信息,如果加上该参数,则不返回多边形的WKT信息。 307 | > 308 | > 说明:只能同时使用RADIUS、MEMBER和GEOM中的一种方式。 309 | 310 | #### 返回值 311 | > 执行成功:命中的目标点数量与WKT信息。 312 | > area不存在:empty list or set。 313 | > 其它情况返回相应的异常信息。 314 | 315 | #### 示例 316 | ``` 317 | 提前执行GIS.ADD Sicily "Palermo" "POINT (13.361389 38.115556)" "Catania" "POINT(15.087269 37.502669)"命令。 318 | 319 | 127.0.0.1:6379> GIS.SEARCH Sicily RADIUS 15 37 200 km WITHDIST ASC 320 | 1) (integer) 2 321 | 2) 1) "Catania" 322 | 2) "POINT(15.087269 37.502669)" 323 | 3) "56.4413" 324 | 4) "Palermo" 325 | 5) "POINT(13.361389 38.115556)" 326 | 6) "190.4424" 327 | 127.0.0.1:6379> 328 | ``` 329 | 330 | ## Tair Modules 331 | [TairHash](https://github.com/alibaba/TairHash): 和redis hash类似,但是可以为field设置expire和version,支持高效的主动过期和被动过期。 332 | [TairZset](https://github.com/alibaba/TairZset): 和redis zset类似,但是支持多(最大255)维排序,同时支持incrby语义,非常适合游戏排行榜场景。 333 | [TairString](https://github.com/alibaba/TairString): 和redis string类似,但是支持设置expire和version,并提供CAS/CAD等实用命令,非常适用于分布式锁等场景。 334 | [TairGis](https://github.com/alibaba/TairGis): 一个支持点、线、面之间相交、包含、被包含关系判断的Redis Module。 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | ## Introduction [中文介绍](README-CN.md) 6 | TairGis is a Redis Module that supports the query of `intersection`, `contains`, and `within` relationships between `points`, `lines`, and `polygons`. It has the following features: 7 | - Fast query performance 8 | - Use `RTree` storage 9 | - The function of Redis `GEORADIUS` [command](https://redis.io/commands/georadius) can be realized through `GIS.SEARCH` 10 | 11 | ## Acknowledgments 12 | Special thanks to https://github.com/tidwall/redis-gis, TairGis relies on redis-gis and fixes some of its problems. 13 | 14 | ## Quick Start 15 | ``` 16 | // Insert a POLYGON 17 | 127.0.0.1:6379> GIS.ADD hangzhou campus 'POLYGON ((120.028041 30.285179, 120.031203 30.28691, 120.037311 30.286239, 120.034185 30.280844))' 18 | (integer) 1 19 | // Query POLYGON 20 | 127.0.0.1:6379> GIS.GET hangzhou campus 21 | "POLYGON((120.028041 30.285179,120.031203 30.28691,120.037311 30.286239,120.034185 30.280844))" 22 | // Determine if the points 'POINT (120.031939 30.285179)' and POLYGON contain 23 | 127.0.0.1:6379> GIS.CONTAINS hangzhou 'POINT (120.031939 30.285179)' 24 | 1) (integer) 1 25 | 2) 1) "campus" 26 | 2) "POLYGON((120.028041 30.285179,120.031203 30.28691,120.037311 30.286239,120.034185 30.280844))" 27 | // Determine if line and POLYGON intersect 28 | 127.0.0.1:6379> GIS.INTERSECTS hangzhou 'LINESTRING (120.029622 30.288952, 120.035488 30.280056)' 29 | 1) (integer) 1 30 | 2) 1) "campus" 31 | 2) "POLYGON((120.028041 30.285179,120.031203 30.28691,120.037311 30.286239,120.034185 30.280844))" 32 | 127.0.0.1:6379> 33 | ``` 34 | 35 | ## BUILD 36 | ``` 37 | make 38 | ``` 39 | After the compilation is successful, the `tairgis.so` file will be generated in the current directory, next, load it with Redis. 40 | ``` 41 | ./redis-server --loadmodule /path/to/tairgis.so 42 | ``` 43 | 44 | ## Test 45 | Edit tests/tairgis.tcl first line: `set testmodule [file your_path/tairgis.so]` 46 | 47 | Copy tairgis.tcl in the tests directory to tests in the redis directory 48 | ``` 49 | cp tests/tairgis.tcl your_redis_path/tests 50 | ``` 51 | Add tairgis to all_tests in test_helper.tcl of redis 52 | ``` 53 | diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl 54 | index 1a5096937..d5a1ba40a 100644 55 | --- a/tests/test_helper.tcl 56 | +++ b/tests/test_helper.tcl 57 | @@ -13,6 +13,7 @@ source tests/support/test.tcl 58 | source tests/support/util.tcl 59 | 60 | set ::all_tests { 61 | + tairgis 62 | ``` 63 | Run ./runtest --single tairgis in the redis root directory 64 | 65 | ## Clients 66 | 67 | | language | GitHub | 68 | |----------|---| 69 | | Java |https://github.com/alibaba/alibabacloud-tairjedis-sdk| 70 | | Python |https://github.com/alibaba/tair-py| 71 | | Go |https://github.com/alibaba/tair-go| 72 | | .Net |https://github.com/alibaba/AlibabaCloud.TairSDK| 73 | 74 | ## API 75 | 76 | ### GIS.ADD 77 | #### Syntax and Complexity 78 | > GIS.ADD area polygonName polygonWkt [polygonName polygonWkt ...] 79 | > Time complexity: O(log n) 80 | 81 | #### Command description 82 | > Add polygons with the specified name (multiple ones can be added) in the area, and use WKT (Well-known text) description. WKT is a text markup language for describing vector geometric objects, 83 | > Spatial reference system and conversion between spatial reference systems. 84 | 85 | #### Parameter Description 86 | > area: a geometric concept. 87 | > polygonName: The name of the polygon. 88 | > polygonWkt: The description information of the polygon, which represents the longitude and latitude of the real world. It is described by WKT (Well-known text) and supports the following types. 89 | > - POINT: Describes the WKT information of a point, such as 'POINT (120.086631 30.138141)', which means that the POINT is located at 120.086631 longitude and 30.138141 latitude. 90 | > - LINESTRING: WKT information describing a line, consisting of two POINTs, such as 'LINESTRING (30 10, 40 40)'. 91 | > - POLYGON: Describes the WKT information of a polygon, consisting of multiple POINTs, such as 'POLYGON ((31 20, 29 20, 29 21, 31 31))'. 92 | > 93 | > Description: The value range of longitude is (-180,180), and the value range of latitude is (-90,90). The following collection types are not supported: MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, GEOMETRY, and COLLECTION. 94 | 95 | #### Return value 96 | > Executed successfully: Returns the number of polygons inserted and updated successfully. 97 | > In other cases, return the corresponding exception information. 98 | 99 | #### Example 100 | ```` 101 | 127.0.0.1:6379> GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))' 102 | (integer) 1 103 | 127.0.0.1:6379> 104 | ```` 105 | 106 | ### GIS.GET 107 | #### Syntax and Complexity 108 | > GIS.ADD area polygonName 109 | > Time complexity: O(1) 110 | 111 | #### Command description 112 | > Get the WKT information of the specified polygon in the target area. 113 | 114 | #### Parameter Description 115 | > area: a geometric concept. 116 | > polygonName: The name of the polygon. 117 | 118 | #### Return value 119 | > Successful execution: WKT information. 120 | > area or polygonName does not exist: nil. 121 | > In other cases, return the corresponding exception information. 122 | 123 | #### Example 124 | ```` 125 | Execute the GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))' command in advance 126 | 127 | 127.0.0.1:6379> GIS.GET hangzhou campus 128 | "POLYGON((30 10,40 40,20 40,10 20,30 10))" 129 | 127.0.0.1:6379> 130 | ```` 131 | 132 | ### GIS.GETALL 133 | #### Syntax and Complexity 134 | > GIS.ADD area [WITHOUTWKT] 135 | > Time complexity: O(n) 136 | 137 | #### Command description 138 | > Get the names and WKT information of all polygons in the target area. If the WITHOUTWKT option is set, only the name of the polygon is returned. 139 | 140 | #### Parameter Description 141 | > area: a geometric concept. 142 | > WITHOUTWKT: It is used to control whether to return the WKT information of the polygon. If this parameter is added, the WKT information of the polygon will not be returned. 143 | 144 | #### Return value 145 | > Successful execution: Returns the polygon name and WKT information. If the WITHOUTWKT option is set, only the polygon name is returned. 146 | > area does not exist: nil. 147 | > In other cases, return the corresponding exception information. 148 | 149 | #### Example 150 | ```` 151 | Execute the GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))' command in advance 152 | 153 | 127.0.0.1:6379>GIS.GETALL hangzhou 154 | 1) "campus" 155 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))" 156 | 127.0.0.1:6379> 157 | ```` 158 | 159 | ### GIS.DEL 160 | #### Syntax and Complexity 161 | > GIS.DEL area polygonName 162 | > Time complexity: O(log n) 163 | 164 | #### Command description 165 | > Delete the specified polygons in the target area. 166 | 167 | #### Parameter Description 168 | > area: a geometric concept. 169 | > polygonName: The name of the polygon. 170 | 171 | #### return value 172 | > Successful execution: OK. 173 | > area or polygonName does not exist: nil. 174 | > In other cases, return the corresponding exception information. 175 | 176 | #### Example 177 | ```` 178 | Execute the GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))' command in advance 179 | 180 | 127.0.0.1:6379> GIS.DEL hangzhou campus 181 | OK 182 | 127.0.0.1:6379> 183 | ```` 184 | 185 | ### GIS.CONTAINS 186 | #### Syntax and Complexity 187 | > GIS.CONTAINS area polygonWkt [WITHOUTWKT] 188 | > Time complexity: O(log M n) at best, O(log n) at worst 189 | 190 | #### Command description 191 | > Determine whether the specified point, line or surface is included in the polygon of the target area. If so, return the number of polygons and polygon information hit in the target area. 192 | 193 | #### Parameter Description 194 | > area: a geometric concept. 195 | > polygonWkt: Specifies the polygon description information to be compared with the target area, using WKT (Well-known text) description, supports the following types. 196 | > - POINT: WKT information describing a point. 197 | > - LINESTRING: WKT information describing a line. 198 | > - POLYGON: WKT information describing a polygon. 199 | > 200 | > WITHOUTWKT: It is used to control whether to return the WKT information of the polygon. If this parameter is added, the WKT information of the polygon will not be returned. 201 | 202 | #### return value 203 | > Successful execution: number of hit polygons and polygon information. 204 | > area does not exist: empty list or set. 205 | > In other cases, return the corresponding exception information. 206 | 207 | #### Example 208 | ```` 209 | Execute the GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))' command in advance 210 | 211 | 127.0.0.1:6379> GIS.CONTAINS hangzhou 'POINT (30 11)' 212 | 1) (integer) 1 213 | 2) 1) "campus" 214 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))" 215 | 127.0.0.1:6379> 216 | ```` 217 | 218 | ### GIS.WITHIN 219 | #### Syntax and Complexity 220 | > GIS.WITHIN area polygonWkt [WITHOUTWKT] 221 | > Time complexity: O(log M n) at best, O(log n) at worst 222 | 223 | #### Command description 224 | > Determine whether the target area is contained in the specified point, line or surface. If so, return the number of polygons and polygon information hit in the target area. 225 | 226 | #### Parameter Description 227 | > area: a geometric concept. 228 | > polygonWkt: Specifies the polygon description information to be compared with the target area, using WKT (Well-known text) description, supports the following types. 229 | > - POINT: WKT information describing a point. 230 | > - LINESTRING: WKT information describing a line. 231 | > - POLYGON: WKT information describing a polygon. 232 | > 233 | > WITHOUTWKT: It is used to control whether to return the WKT information of the polygon. If this parameter is added, the WKT information of the polygon will not be returned. 234 | 235 | #### return value 236 | > Successful execution: number of hit polygons and polygon information. 237 | > area does not exist: empty list or set. 238 | > In other cases, return the corresponding exception information. 239 | 240 | #### Example 241 | ```` 242 | Execute the GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))' command in advance 243 | 244 | 127.0.0.1:6379> GIS.WITHIN hangzhou 'POLYGON ((30 5, 50 50, 20 50, 5 20, 30 5))' 245 | 1) (integer) 1 246 | 2) 1) "campus" 247 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))" 248 | 127.0.0.1:6379> 249 | ```` 250 | 251 | ### GIS.INTERSECTS 252 | #### Syntax and Complexity 253 | > GIS.INTERSECTS area polygonWkt [WITHOUTWKT] 254 | > Time complexity: O(log M n) at best, O(log n) at worst 255 | 256 | #### Command description 257 | > Determine whether the specified point, line or surface intersects with the polygon of the target area. If so, return the number of polygons and polygon information in the target area that intersect with it. 258 | 259 | #### Parameter Description 260 | > area: a geometric concept. 261 | > polygonWkt: Specifies the polygon description information to be compared with the target area, using WKT (Well-known text) description, supports the following types. 262 | > - POINT: WKT information describing a point. 263 | > - LINESTRING: WKT information describing a line. 264 | > - POLYGON: WKT information describing a polygon. 265 | > 266 | > WITHOUTWKT: It is used to control whether to return the WKT information of the polygon. If this parameter is added, the WKT information of the polygon will not be returned. 267 | 268 | #### return value 269 | > Successful execution: number of hit polygons and polygon information. 270 | > area does not exist: empty list or set. 271 | > In other cases, return the corresponding exception information. 272 | 273 | #### Example 274 | ```` 275 | Execute the GIS.ADD hangzhou campus 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))' command in advance 276 | 277 | 127.0.0.1:6379> GIS.INTERSECTS hangzhou 'LINESTRING (30 10, 40 40)' 278 | 1) (integer) 1 279 | 2) 1) "campus" 280 | 2) "POLYGON((30 10,40 40,20 40,10 20,30 10))" 281 | 127.0.0.1:6379> 282 | ```` 283 | 284 | ### GIS.SEARCH 285 | #### Syntax and Complexity 286 | > GIS.SEARCH area [RADIUS longitude latitude distance m|km|ft|mi] 287 | > [MEMBER field distance m|km|ft|mi] 288 | > [GEOM geom] 289 | > [COUNT count] 290 | > [LIMIT limit] 291 | > [ASC|DESC] 292 | > [WITHDIST] 293 | > [WITHOUTWKT] 294 | > Time complexity: O(log M n) at best, O(log n) at worst 295 | 296 | #### Command description 297 | > Find the point in the target area within the specified longitude, latitude and radius distance. 298 | 299 | #### Parameter Description 300 | > area: a geometric concept. 301 | > RADIUS: Enter longitude, latitude, distance and radius units (m for meters, km for kilometers, ft for feet, mi for miles) to search, for example RADIUS 15 37 200 km . 302 | > MEMBER: Select the existing POINT in the current area as the search origin, and specify the radius to search. The value order is polygon name (field), radius (distance), radius unit (m means meter, km means kilometer, ft means feet, mi for miles), such as MEMBER Agrigento 100 km. 303 | > GEOM: Set the search range according to the WKT format, which can be any polygon, such as GEOM 'POLYGON((10 30,20 30,20 40,10 40))'. 304 | > COUNT: Used to limit the number of returned items, such as COUNT 3. 305 | > LIMIT: The difference between Limit and Count is: Limit is completed during the search process, as long as limit elements are searched, the search will stop; but Count is filtering after searching all elements. 306 | > ASC|DESC: Used to control the return information to be sorted by distance. ASC means sorting from near to far according to the center position; DESC means sorting from far to near. 307 | > WITHDIST: Used to control whether to return the distance between the target point and the search origin. 308 | > WITHOUTWKT: It is used to control whether to return the WKT information of the polygon. If this parameter is added, the WKT information of the polygon will not be returned. 309 | > 310 | > Note: Only one of RADIUS, MEMBER and GEOM can be used at the same time. 311 | 312 | #### Return value 313 | > Successful execution: the number of target points hit and WKT information. 314 | > area does not exist: empty list or set. 315 | > In other cases, return the corresponding exception information. 316 | 317 | #### Example 318 | ```` 319 | Execute the GIS.ADD Sicily "Palermo" "POINT (13.361389 38.115556)" "Catania" "POINT(15.087269 37.502669)" command in advance. 320 | 321 | 127.0.0.1:6379> GIS.SEARCH Sicily RADIUS 15 37 200 km WITHDIST ASC 322 | 1) (integer) 2 323 | 2) 1) "Catania" 324 | 2) "POINT(15.087269 37.502669)" 325 | 3) "56.4413" 326 | 4) "Palermo" 327 | 5) "POINT(13.361389 38.115556)" 328 | 6) "190.4424" 329 | 127.0.0.1:6379> 330 | ```` 331 | 332 | ## Tair Modules 333 | [TairHash](https://github.com/alibaba/TairHash): A redis module, similar to redis hash, but you can set expire and version for the field. 334 | [TairZset](https://github.com/alibaba/TairZset): A redis module, similar to redis zset, but you can set multiple scores for each member to support multi-dimensional sorting. 335 | [TairString](https://github.com/alibaba/TairString): A redis module, similar to redis string, but you can set expire and version for the value. It also provides many very useful commands, such as cas/cad, etc. 336 | [TairGis](https://github.com/alibaba/TairGis): A redis module that supports the query of `intersection`, `contains`, and `within` relationships between `points`, `lines`, and `polygons`. -------------------------------------------------------------------------------- /imgs/tairgis_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tair-opensource/TairGis/16b1dba038c7bac59b52fc823ab7d368a96bdb3a/imgs/tairgis_logo.png -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SOURCE_FILES 2 | tairgis.c 3 | spatial.c 4 | util.c 5 | spatial/geom.c 6 | spatial/grisu3.c 7 | spatial/rtree.c 8 | spatial/geoutil.c 9 | spatial/poly.c 10 | spatial/polyinside.c 11 | spatial/polyintersects.c 12 | spatial/polyraycast.c 13 | spatial/hash.c 14 | spatial/bing.c 15 | spatial/json.c) 16 | 17 | set(CMAKE_MACOSX_RPATH 1) 18 | add_library(tairgis SHARED ${SOURCE_FILES}) -------------------------------------------------------------------------------- /src/spatial.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Alibaba Tair Team 3 | * Copyright (c) 2016, Josh Baker 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following 10 | * disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the 13 | * following disclaimer in the documentation and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote 16 | * products derived from this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 19 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "spatial.h" 32 | #include "util.h" 33 | #include "tairgis.h" 34 | 35 | int matchSearch( 36 | geom g, geomPolyMap *targetMap, 37 | int targetType, int searchType, 38 | geomCoord center, double meters 39 | ); 40 | 41 | spatial *spatialNew() { 42 | spatial *s = RedisModule_Alloc(sizeof(spatial)); 43 | if (!s) return NULL; 44 | s->h = RedisModule_CreateDict(NULL); 45 | s->keyhash = RedisModule_CreateDict(NULL); 46 | s->idxhash = RedisModule_CreateDict(NULL); 47 | s->tr = rtreeNew(); 48 | s->fences = NULL; 49 | if (!s->tr) { 50 | spatialFree(s); 51 | return NULL; 52 | } 53 | return s; 54 | } 55 | 56 | void redisModuleDictFree(RedisModuleDict *h) { 57 | if (!h) return; 58 | size_t keylen; 59 | void *data; 60 | RedisModuleDictIter *iter = RedisModule_DictIteratorStartC( 61 | h, "^", NULL, 0); 62 | while (RedisModule_DictNextC(iter, &keylen, &data)) { 63 | GisModule_FreeStringSafe(NULL, (RedisModuleString *) data); 64 | } 65 | RedisModule_FreeDict(NULL, h); 66 | RedisModule_DictIteratorStop(iter); 67 | } 68 | 69 | void spatialFree(spatial *s) { 70 | if (s) { 71 | if (s->h) redisModuleDictFree(s->h); 72 | if (s->keyhash) redisModuleDictFree(s->keyhash); 73 | if (s->idxhash) redisModuleDictFree(s->idxhash); 74 | if (s->tr) rtreeFree(s->tr); 75 | /* do not free the fence object, only the array. 76 | * seems there exists some mem leak */ 77 | if (s->fences) RedisModule_Free(s->fences); 78 | RedisModule_Free(s); 79 | } 80 | } 81 | 82 | // get an sds based on the key. 83 | // return value must be freed by the caller. 84 | static RedisModuleString * hashTypeGetRedisModuleString(RedisModuleDict *o, RedisModuleString *key) { 85 | int nokey = 0; 86 | RedisModuleString *vstr = RedisModule_DictGet(o, key, &nokey); 87 | if (nokey == 1 || !vstr) return NULL; 88 | return vstr; 89 | } 90 | 91 | // returns a raw pointer to the value 92 | static const void *hashTypeGetRaw(RedisModuleDict *o, RedisModuleString *key) { 93 | int nokey = 0; 94 | RedisModuleString *vstr = RedisModule_DictGet(o, key, &nokey); 95 | if (nokey == 1 || !vstr) return NULL; 96 | return RedisModule_StringPtrLen(vstr, NULL); 97 | } 98 | 99 | int spatialTypeSet(ExGisObj *o, RedisModuleString *field, RedisModuleString *val) { 100 | geom g; 101 | geomRect r; 102 | RedisModuleString *sidx; 103 | uint64_t nidx; 104 | spatial *s = o->s; 105 | 106 | g = (geom) RedisModule_StringPtrLen(val, NULL); 107 | r = geomBounds(g); 108 | 109 | /* try to delete the former existing data for 'field' 110 | * also remove what's exiting in rtree */ 111 | spatialTypeDelete(o, field, &r, NULL); 112 | 113 | /* create a new idx / field entry */ 114 | s->idx++; 115 | nidx = (uint64_t) s->idx; 116 | sidx = RedisModule_CreateString(NULL, (const char *) &nidx, 8); 117 | 118 | GisModule_DictInsertOrUpdate(s->idxhash, sidx, field); 119 | GisModule_DictInsertOrUpdate(s->keyhash, field, sidx); 120 | GisModule_FreeStringSafe(NULL, sidx); 121 | 122 | GisModule_DictInsertOrUpdate(s->h, field, val); 123 | 124 | /* update the rtree */ 125 | rtreeInsert(s->tr, r.min.x, r.min.y, r.max.x, r.max.y, s->idx); 126 | 127 | return 1; 128 | } 129 | 130 | RedisModuleString *decodeOrReply(RedisModuleCtx *ctx, const char *value) { 131 | geom g = NULL; 132 | int sz = 0; 133 | geomErr err = geomDecode(value, strlen(value), 0, &g, &sz); 134 | if (err != GEOM_ERR_NONE) { 135 | RedisModule_ReplyWithError(ctx, "ERR invalid geometry"); 136 | return NULL; 137 | } 138 | RedisModuleString *gvalue = RedisModule_CreateString(NULL, g, (size_t) sz); 139 | geomFree(g); 140 | return gvalue; 141 | } 142 | 143 | int matchSearch( 144 | geom g, geomPolyMap *targetMap, 145 | int targetType, int searchType, 146 | geomCoord center, double meters 147 | ) { 148 | int match = 0; 149 | if (geomIsSimplePoint(g) && targetType == RADIUS) { 150 | match = geomCoordWithinRadius(geomCenter(g), center, meters); 151 | } else { 152 | geomPolyMap *m = geomNewPolyMap(g); 153 | if (!m) { 154 | return 0; 155 | } 156 | if (searchType == WITHIN) { 157 | match = geomPolyMapWithin(m, targetMap); 158 | } else if (searchType == INTERSECTS) { 159 | match = geomPolyMapIntersects(m, targetMap); 160 | } else if(searchType == EX_CONTAINS) { 161 | match = geomPolyMapContains(m,targetMap); 162 | } else if(searchType == EX_INTERSECTS) { 163 | match = geomPolyMapExIntersects(m,targetMap); 164 | } 165 | geomFreePolyMap(m); 166 | } 167 | return match; 168 | } 169 | 170 | int RedisModule_StringPtrIsEmpty(const char *str) { 171 | return strcmp(str, "(NULL string reply referenced in module)"); 172 | } 173 | 174 | int spatialTypeDelete(ExGisObj *o, RedisModuleString *field, geomRect *rin, int *isEmpty) { 175 | geomRect r; 176 | RedisModuleString *sidx; 177 | char *idx; 178 | int res; 179 | spatial *s = o->s; 180 | const char *cstr = NULL; 181 | size_t len = 0; 182 | 183 | /* get the idx */ 184 | sidx = hashTypeGetRedisModuleString(s->keyhash, field); 185 | cstr = RedisModule_StringPtrLen(sidx, &len); 186 | if (!RedisModule_StringPtrIsEmpty(cstr) || len != 8) return 0; 187 | idx = (char *) (*((uint64_t *) cstr)); 188 | 189 | geom g = NULL; 190 | if (rin) { 191 | r = *rin; 192 | } else { 193 | const void *raw = hashTypeGetRaw(s->h, field); 194 | if (!raw) return 0; 195 | g = (geom) raw; 196 | r = geomBounds(g); 197 | } 198 | 199 | rtreeRemove(s->tr, r.min.x, r.min.y, r.max.x, r.max.y, idx); 200 | 201 | RedisModuleString *o1 = NULL, *o2 = NULL, *o3 = NULL; 202 | res = RedisModule_DictDel(s->h, field, &o1) || RedisModule_DictDel(s->idxhash, sidx, &o2) || 203 | RedisModule_DictDel(s->keyhash, field, &o3); 204 | 205 | GisModule_FreeStringSafe(NULL, o1); 206 | GisModule_FreeStringSafe(NULL, o2); 207 | GisModule_FreeStringSafe(NULL, o3); 208 | 209 | if (RedisModule_DictSize(s->keyhash) == 0 && isEmpty) { 210 | *isEmpty = 1; 211 | } 212 | 213 | return res == REDISMODULE_OK ? 1 : 0; 214 | } 215 | 216 | /* Importing some stuff from t_hash.c but these should exist in server.h */ 217 | void addGeomReplyBulkCBuffer(RedisModuleCtx *ctx, const void *p) { 218 | char *wkt = geomEncodeWKT((geom) p, 0); 219 | if (!wkt) { 220 | RedisModule_ReplyWithError(ctx, "ERR failed to encode wkt"); 221 | return; 222 | } 223 | RedisModule_ReplyWithStringBuffer(ctx, wkt, strlen(wkt)); 224 | geomFreeWKT(wkt); 225 | } 226 | 227 | /* These are direct copies from t_hash.c because they're defined as static and 228 | * I didn't want to change the source file. */ 229 | void addGeomHashFieldToReply(RedisModuleCtx *ctx, ExGisObj *o, RedisModuleString *field) { 230 | if (o == NULL) { 231 | RedisModule_ReplyWithNull(ctx); 232 | return; 233 | } 234 | RedisModuleString *vstr = NULL; 235 | int nokey = 0; 236 | 237 | vstr = RedisModule_DictGet(o->s->h, field, &nokey); 238 | if (nokey == 1 || !vstr) { 239 | RedisModule_ReplyWithNull(ctx); 240 | return; 241 | } 242 | 243 | addGeomReplyBulkCBuffer(ctx, RedisModule_StringPtrLen(vstr, NULL)); 244 | } 245 | 246 | void addGeomHashAllToReply(RedisModuleCtx *ctx, ExGisObj *o, int flag) { 247 | if (o == NULL) { 248 | RedisModule_ReplyWithNull(ctx); 249 | return; 250 | } 251 | 252 | long size = (long) RedisModule_DictSize(o->s->h); 253 | RedisModule_ReplyWithArray(ctx, flag & GIS_WITHVALUE ? size * 2 : size); 254 | 255 | void *field, *val; 256 | RedisModuleDictIter *iter = RedisModule_DictIteratorStartC( 257 | o->s->h, "^", NULL, 0); 258 | while ((field = RedisModule_DictNext(NULL, iter, &val)) != NULL) { 259 | RedisModule_ReplyWithString(ctx, field); 260 | if (flag & GIS_WITHVALUE) { 261 | addGeomReplyBulkCBuffer(ctx, RedisModule_StringPtrLen(val, NULL)); 262 | } 263 | GisModule_FreeStringSafe(NULL, field); 264 | } 265 | RedisModule_DictIteratorStop(iter); 266 | } 267 | 268 | /* Glob-style pattern matching. */ 269 | // Move from redis src/util.c 270 | static int stringmatchlen(const char *pattern, int patternLen, 271 | const char *string, int stringLen, int nocase) 272 | { 273 | while(patternLen && stringLen) { 274 | switch(pattern[0]) { 275 | case '*': 276 | while (pattern[1] == '*') { 277 | pattern++; 278 | patternLen--; 279 | } 280 | if (patternLen == 1) 281 | return 1; /* match */ 282 | while(stringLen) { 283 | if (stringmatchlen(pattern+1, patternLen-1, 284 | string, stringLen, nocase)) 285 | return 1; /* match */ 286 | string++; 287 | stringLen--; 288 | } 289 | return 0; /* no match */ 290 | break; 291 | case '?': 292 | if (stringLen == 0) 293 | return 0; /* no match */ 294 | string++; 295 | stringLen--; 296 | break; 297 | case '[': 298 | { 299 | int not, match; 300 | 301 | pattern++; 302 | patternLen--; 303 | not = pattern[0] == '^'; 304 | if (not) { 305 | pattern++; 306 | patternLen--; 307 | } 308 | match = 0; 309 | while(1) { 310 | if (pattern[0] == '\\' && patternLen >= 2) { 311 | pattern++; 312 | patternLen--; 313 | if (pattern[0] == string[0]) 314 | match = 1; 315 | } else if (pattern[0] == ']') { 316 | break; 317 | } else if (patternLen == 0) { 318 | pattern--; 319 | patternLen++; 320 | break; 321 | } else if (pattern[1] == '-' && patternLen >= 3) { 322 | int start = pattern[0]; 323 | int end = pattern[2]; 324 | int c = string[0]; 325 | if (start > end) { 326 | int t = start; 327 | start = end; 328 | end = t; 329 | } 330 | if (nocase) { 331 | start = tolower(start); 332 | end = tolower(end); 333 | c = tolower(c); 334 | } 335 | pattern += 2; 336 | patternLen -= 2; 337 | if (c >= start && c <= end) 338 | match = 1; 339 | } else { 340 | if (!nocase) { 341 | if (pattern[0] == string[0]) 342 | match = 1; 343 | } else { 344 | if (tolower((int)pattern[0]) == tolower((int)string[0])) 345 | match = 1; 346 | } 347 | } 348 | pattern++; 349 | patternLen--; 350 | } 351 | if (not) 352 | match = !match; 353 | if (!match) 354 | return 0; /* no match */ 355 | string++; 356 | stringLen--; 357 | break; 358 | } 359 | case '\\': 360 | if (patternLen >= 2) { 361 | pattern++; 362 | patternLen--; 363 | } 364 | /* fall through */ 365 | default: 366 | if (!nocase) { 367 | if (pattern[0] != string[0]) 368 | return 0; /* no match */ 369 | } else { 370 | if (tolower((int)pattern[0]) != tolower((int)string[0])) 371 | return 0; /* no match */ 372 | } 373 | string++; 374 | stringLen--; 375 | break; 376 | } 377 | pattern++; 378 | patternLen--; 379 | if (stringLen == 0) { 380 | while(*pattern == '*') { 381 | pattern++; 382 | patternLen--; 383 | } 384 | break; 385 | } 386 | } 387 | if (patternLen == 0 && stringLen == 0) 388 | return 1; 389 | return 0; 390 | } 391 | 392 | int searchIterator(double minX, double minY, double maxX, double maxY, void *item, void *userdata) { 393 | (void)(minX);(void)(minY);(void)(maxX);(void)(maxY); // unused vars. 394 | 395 | searchContext *ctx = userdata; 396 | 397 | /* if limit reach, just return */ 398 | if ((ctx->limit != 0) && (ctx->len >= ctx->limit)) { 399 | return 1; 400 | } 401 | 402 | /* retrieve the field */ 403 | int nokey = 0; 404 | uint64_t nidx = (uint64_t)item; 405 | RedisModuleString *sidx = RedisModule_CreateString(NULL, (const char *) &nidx, 8); 406 | RedisModuleString *field = RedisModule_DictGet(ctx->s->idxhash, sidx, &nokey); 407 | GisModule_FreeStringSafe(NULL, sidx); 408 | if (nokey == 1) { 409 | return 1; 410 | } 411 | 412 | const char *fieldStr = NULL, *valueStr = NULL; 413 | size_t filedLen, valueLen; 414 | fieldStr = RedisModule_StringPtrLen(field, &filedLen); 415 | 416 | if (!(ctx->allfields || 417 | stringmatchlen(ctx->pattern, (int) strlen(ctx->pattern), fieldStr, (int) filedLen, 0))) { 418 | return 1; 419 | } 420 | 421 | // retrieve the geom 422 | RedisModuleString *value = RedisModule_DictGet(ctx->s->h, field, &nokey); 423 | valueStr = RedisModule_StringPtrLen(value, &valueLen); 424 | if (nokey == 1){ 425 | return 1; 426 | } 427 | 428 | geom g = (geom)valueStr; 429 | 430 | int match = matchSearch(g, ctx->m, ctx->targetType, ctx->searchType, ctx->center, ctx->meters); 431 | if (!match){ 432 | return 1; 433 | } 434 | // append item 435 | if (ctx->len == ctx->cap) { 436 | int ncap = ctx->cap; 437 | if (ncap == 0){ 438 | ncap = 1; 439 | } else { 440 | ncap *= 2; 441 | } 442 | resultItem *nresults = RedisModule_Realloc(ctx->results, ncap*sizeof(resultItem)); 443 | if (!nresults){ 444 | RedisModule_ReplyWithError(ctx->c, "ERR out of memory"); 445 | ctx->fail = 1; 446 | return 0; 447 | } 448 | ctx->results = nresults; 449 | ctx->cap = ncap; 450 | } 451 | 452 | ctx->results[ctx->len].field = field; 453 | ctx->results[ctx->len].value = value; 454 | ctx->len++; 455 | 456 | return 1; 457 | } 458 | 459 | int sortDistanceAsc(const void *a, const void *b) { 460 | resultItem *da = (resultItem *)a, *db = (resultItem *)b; 461 | if (da->distance > db->distance) { 462 | return 1; 463 | } else if (fabs(da->distance - db->distance) < 0.0000001) { 464 | return 0; 465 | } else { 466 | return -1; 467 | } 468 | } 469 | 470 | int sortDistanceDesc(const void *a, const void *b) { 471 | return -sortDistanceAsc(a, b); 472 | } 473 | -------------------------------------------------------------------------------- /src/spatial.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Alibaba Tair Team 3 | * Copyright (c) 2016, Josh Baker 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following 10 | * disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the 13 | * following disclaimer in the documentation and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote 16 | * products derived from this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 19 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef SPATIAL_H 28 | #define SPATIAL_H 29 | 30 | #include "spatial/rtree.h" 31 | #include "spatial/geoutil.h" 32 | #include "spatial/geom.h" 33 | #include "spatial/hash.h" 34 | #include "spatial/bing.h" 35 | #include "redismodule.h" 36 | 37 | #define FENCE_ENTER (1<<1) 38 | #define FENCE_EXIT (1<<2) 39 | #define FENCE_CROSS (1<<3) 40 | #define FENCE_INSIDE (1<<4) 41 | #define FENCE_OUTSIDE (1<<5) 42 | #define FENCE_KEYDEL (1<<6) 43 | #define FENCE_FIELDDEL (1<<7) 44 | #define FENCE_FLUSH (1<<8) 45 | #define FENCE_ALL (FENCE_ENTER|FENCE_EXIT|FENCE_CROSS|\ 46 | FENCE_INSIDE|FENCE_OUTSIDE|FENCE_KEYDEL|\ 47 | FENCE_FIELDDEL|FENCE_FIELDDEL) 48 | 49 | #define FENCE_NOTIFY_SET 1 50 | #define FENCE_NOTIFY_DEL 2 51 | 52 | #define WITHIN 1 53 | #define INTERSECTS 2 54 | #define EX_CONTAINS 3 55 | #define EX_INTERSECTS 4 56 | 57 | #define RADIUS 1 58 | #define GEOMETRY 2 59 | #define BOUNDS 3 60 | 61 | #define OUTPUT_COUNT 1 62 | #define OUTPUT_FIELD 2 63 | #define OUTPUT_WKT 3 64 | #define OUTPUT_WKB 4 65 | #define OUTPUT_JSON 5 66 | #define OUTPUT_POINT 6 67 | #define OUTPUT_BOUNDS 7 68 | #define OUTPUT_HASH 8 69 | #define OUTPUT_QUAD 9 70 | #define OUTPUT_TILE 10 71 | 72 | typedef struct fence { 73 | //robj *channel; 74 | int allfields; 75 | char *pattern; 76 | int targetType; 77 | geomCoord center; 78 | double meters; 79 | int searchType; 80 | geom g; 81 | int sz; 82 | geomPolyMap *m; 83 | } fence; 84 | 85 | typedef struct spatial { 86 | RedisModuleDict *h; // main hash store that persists to RDB. 87 | rtree *tr; // underlying spatial index. 88 | fence **fences; // the stored fences 89 | int fcap, flen; // the cap/len for fence array 90 | 91 | // The following fields are for mapping an idx to a key and 92 | // vice versa. The rtree expects that each entry has a pointer to 93 | // an object in memory. This should be the base address of the 94 | // sds key that is stored in the main hash store, that way there is 95 | // no extra memory overhead except a pointer. But at the moment I'm 96 | // 100% sure how safe it is to expect that a key in the hash will 97 | // not change it's base pointer address. So until further testing 98 | // around hash key stability we will increment an idx pointer and 99 | // map this value to a key, then assign this idx value to each 100 | // rtree entry. This allows a reverse lookup to the key. This is a 101 | // safe (albeit slower and higher mem usage) way to track entries. 102 | char *idx; // pointer that acts as a private id for entries. 103 | RedisModuleDict *keyhash; // stores key -> idx 104 | RedisModuleDict *idxhash; // stores idx -> key 105 | } spatial; 106 | 107 | typedef struct resultItem { 108 | RedisModuleString *field; 109 | RedisModuleString *value; 110 | double distance; 111 | } resultItem; 112 | 113 | typedef struct searchContext { 114 | spatial *s; 115 | RedisModuleCtx *c; 116 | int searchType; 117 | int targetType; 118 | int fail; 119 | int len; 120 | int cap; 121 | resultItem *results; 122 | long long cursor; 123 | char *pattern; 124 | int allfields; 125 | int output; 126 | int precision; 127 | int nofields; 128 | int fence; 129 | int releaseg; 130 | 131 | // bounds 132 | geomRect bounds; 133 | 134 | // radius 135 | geomCoord center; 136 | double meters; 137 | double to_meters; 138 | 139 | // geometry 140 | geom g; 141 | int sz; 142 | geomPolyMap *m; 143 | 144 | // flags eg. WITHOUTWKT 145 | int flag; 146 | long long count; 147 | long long limit; 148 | 149 | } searchContext; 150 | 151 | typedef struct ExGisObj { 152 | spatial *s; 153 | } ExGisObj; 154 | 155 | spatial *spatialNew(); 156 | void spatialFree(spatial *s); 157 | int spatialTypeSet(ExGisObj *o, RedisModuleString *field, RedisModuleString *val); 158 | int spatialTypeDelete(ExGisObj *o, RedisModuleString *field, geomRect *rin, int *isEmpty); 159 | RedisModuleString *decodeOrReply(RedisModuleCtx *ctx, const char *value); 160 | void addGeomHashFieldToReply(RedisModuleCtx *ctx, ExGisObj *o, RedisModuleString *field); 161 | void addGeomHashAllToReply(RedisModuleCtx *ctx, ExGisObj *o, int flag); 162 | int searchIterator(double minX, double minY, double maxX, double maxY, void *item, void *userdata); 163 | int sortDistanceAsc(const void *a, const void *b); 164 | int sortDistanceDesc(const void *a, const void *b); 165 | 166 | #endif // SPATIAL_H 167 | 168 | -------------------------------------------------------------------------------- /src/spatial/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.dSYM 3 | test 4 | *.o 5 | a.out -------------------------------------------------------------------------------- /src/spatial/Makefile: -------------------------------------------------------------------------------- 1 | STD= 2 | WARN= -Wall -Wno-strict-aliasing -Wno-typedef-redefinition 3 | OPT= -O2 -std=c99 4 | 5 | R_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) 6 | R_LDFLAGS= $(LDFLAGS) 7 | DEBUG= -g 8 | 9 | R_CC=$(CC) $(R_CFLAGS) 10 | R_LD=$(CC) $(R_LDFLAGS) 11 | 12 | all: geom.o grisu3.o rtree.o geoutil.o \ 13 | poly.o polyinside.o polyraycast.o polyintersects.o \ 14 | hash.o bing.o json.o 15 | testapp: all 16 | -@$(R_CC) -o test test.c grisu3.o -I. \ 17 | geom_test.c geom.o \ 18 | rtree_test.c rtree.o \ 19 | geoutil_test.c geoutil.o \ 20 | json.o \ 21 | polyinside_test.c polyintersects_test.c poly_test.c \ 22 | poly.o polyinside.o polyraycast.o polyintersects.o \ 23 | -lm 24 | 25 | test: testapp 26 | -@./test 27 | 28 | .PHONY: all 29 | 30 | geom.o: geom.h geom.c geom_levels.c geom_polymap.c geom_json.c 31 | grisu3.o: grisu3.h grisu3.c 32 | rtree.o: rtree.h rtree.c rtree_tmpl.c 33 | geoutil.o: geoutil.h geoutil.c 34 | poly.o: poly.h poly.c 35 | polyinside.o: poly.h polyinside.c 36 | polyraycast.o: poly.h polyraycast.c 37 | polyintersects.o: poly.h polyintersects.c 38 | hash.o: hash.h hash.c 39 | bing.o: bing.h bing.c 40 | json.o: json.h json.c 41 | 42 | .c.o: 43 | $(R_CC) -c $< 44 | 45 | clean: 46 | rm -f *.o 47 | -------------------------------------------------------------------------------- /src/spatial/bing.c: -------------------------------------------------------------------------------- 1 | // https://msdn.microsoft.com/en-us/library/bb259689.aspx 2 | 3 | #include 4 | #include 5 | #include "bing.h" 6 | 7 | #ifndef PI 8 | #define PI 3.14159265358979323846 9 | #endif 10 | 11 | 12 | //static const double EarthRadius = 6378137.0; 13 | static const double MinLatitude = -85.05112878; 14 | static const double MaxLatitude = 85.05112878; 15 | static const double MinLongitude = -180.0; 16 | static const double MaxLongitude = 180.0; 17 | static const int TileSize = 256; 18 | //static const int MaxLevelOfDetail = 38; 19 | 20 | 21 | static double clip(double n, double minValue, double maxValue) { 22 | if (n < minValue) { 23 | return minValue; 24 | } 25 | if (n > maxValue) { 26 | return maxValue; 27 | } 28 | return n; 29 | } 30 | 31 | int bingMapSize(int levelOfDetail) { 32 | return TileSize << levelOfDetail; 33 | } 34 | 35 | void bingLatLongToPixelXY(double latitude, double longitude, int levelOfDetail, int *pixelX, int *pixelY) { 36 | latitude = clip(latitude, MinLatitude, MaxLatitude); 37 | longitude = clip(longitude, MinLongitude, MaxLongitude); 38 | double x = (longitude + 180) / 360; 39 | double sinLatitude = sin(latitude * PI / 180); 40 | double y = 0.5 - log((1+sinLatitude)/(1-sinLatitude))/(4*PI); 41 | double mapSize = bingMapSize(levelOfDetail); 42 | if (pixelX) *pixelX = clip(x*mapSize+0.5, 0, mapSize-1); 43 | if (pixelY) *pixelY = clip(y*mapSize+0.5, 0, mapSize-1); 44 | } 45 | 46 | void bingPixelXYToLatLong(int pixelX, int pixelY, int levelOfDetail, double *latitude, double *longitude) { 47 | double mapSize = bingMapSize(levelOfDetail); 48 | double x = (clip(pixelX, 0, mapSize-1) / mapSize) - 0.5; 49 | double y = 0.5 - (clip(pixelY, 0, mapSize-1) / mapSize); 50 | if (latitude) *latitude = 90 - 360*atan(exp(-y*2*PI))/PI; 51 | if (longitude) *longitude = 360 * x; 52 | } 53 | 54 | void bingPixelXYToTileXY(int pixelX, int pixelY, int *tileX, int *tileY) { 55 | if (tileX) *tileX = pixelX >> 8; 56 | if (tileY) *tileY = pixelY >> 8; 57 | } 58 | 59 | void bingTileXYToPixelXY(int tileX, int tileY, int *pixelX, int *pixelY) { 60 | if (pixelX) *pixelX = tileX << 8; 61 | if (pixelY) *pixelY = tileY << 8; 62 | } 63 | 64 | void bingTileXYToQuadKey(int tileX, int tileY, int levelOfDetail, char *quadKey) { 65 | for (int i=levelOfDetail,j=0;i>0;i--,j++) { 66 | int mask = 1 << (i - 1); 67 | if ((tileX & mask) != 0) { 68 | if ((tileY & mask) != 0) { 69 | quadKey[j] = '3'; 70 | } else { 71 | quadKey[j] = '1'; 72 | } 73 | } else if ((tileY & mask) != 0) { 74 | quadKey[j] = '2'; 75 | } else { 76 | quadKey[j] = '0'; 77 | } 78 | } 79 | } 80 | 81 | int bingQuadKeyToTileXY(char *quadKey, int *tileX, int *tileY, int *levelOfDetail) { 82 | int ltileX = 0; 83 | int ltileY = 0; 84 | int llevelOfDetail = strlen(quadKey); 85 | for (int i=llevelOfDetail;i>0;i--) { 86 | int mask = 1 << (i - 1); 87 | switch (quadKey[llevelOfDetail-i]) { 88 | case '0': 89 | break; 90 | case '1': 91 | ltileX |= mask; 92 | break; 93 | case '2': 94 | ltileY |= mask; 95 | break; 96 | case '3': 97 | ltileX |= mask; 98 | ltileY |= mask; 99 | break; 100 | default: 101 | return 0; 102 | } 103 | } 104 | if (tileX) *tileX = ltileX; 105 | if (tileY) *tileY = ltileY; 106 | if (levelOfDetail) *levelOfDetail = llevelOfDetail; 107 | 108 | return 1; 109 | } 110 | 111 | void bingTileXYToBounds(int tileX, int tileY, int levelOfDetail, double *minLat, double *minLon, double *maxLat, double *maxLon) { 112 | int size = 1 << levelOfDetail; 113 | int pixelX, pixelY; 114 | bingTileXYToPixelXY(tileX, tileY, &pixelX, &pixelY); 115 | bingPixelXYToLatLong(pixelX, pixelY, levelOfDetail, maxLat, minLon); 116 | bingTileXYToPixelXY(tileX+1, tileY+1, &pixelX, &pixelY); 117 | bingPixelXYToLatLong(pixelX, pixelY, levelOfDetail, minLat, maxLon); 118 | if (tileX%size == 0) { 119 | if (minLon) *minLon = MinLongitude; 120 | } 121 | if (tileX%size == size-1) { 122 | if (maxLon) *maxLon = MaxLongitude; 123 | } 124 | if (tileY <= 0) { 125 | if (maxLat) *maxLat = MaxLatitude; 126 | } 127 | if (tileY >= size-1) { 128 | if (minLat) *minLat = MinLatitude; 129 | } 130 | } 131 | 132 | int bingQuadKeyToBounds(char *quadkey, double *minLat, double *minLon, double *maxLat, double *maxLon){ 133 | int tileX, tileY, levelOfDetail; 134 | if (!bingQuadKeyToTileXY(quadkey, &tileX, &tileY, &levelOfDetail)){ 135 | return 0; 136 | } 137 | bingTileXYToBounds(tileX, tileY, levelOfDetail, minLat, minLon, maxLat, maxLon); 138 | return 1; 139 | } 140 | 141 | void bingLatLonToTileXY(double latitude, double longitude, int levelOfDetail, int *tileX, int *tileY){ 142 | int pixelX, pixelY; 143 | bingLatLongToPixelXY(latitude, longitude, levelOfDetail, &pixelX, &pixelY); 144 | bingPixelXYToTileXY(pixelX, pixelY, tileX, tileY); 145 | } 146 | 147 | void bingLatLongToQuadKey(double latitude, double longitude, int levelOfDetail, char *quadkey){ 148 | int tileX, tileY; 149 | bingLatLonToTileXY(latitude, longitude, levelOfDetail, &tileX, &tileY); 150 | bingTileXYToQuadKey(tileX, tileY, levelOfDetail, quadkey); 151 | } 152 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /src/spatial/bing.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016, Josh Baker . 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * * Neither the name of Redis nor the names of its contributors may be used 14 | * to endorse or promote products derived from this software without 15 | * specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 | * THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef BING_H_ 31 | #define BING_H_ 32 | 33 | #if defined(__cplusplus) 34 | extern "C" { 35 | #endif 36 | 37 | int bingMapSize(int levelOfDetail); 38 | void bingLatLongToPixelXY(double latitude, double longitude, int levelOfDetail, int *pixelX, int *pixelY); 39 | void bingPixelXYToLatLong(int pixelX, int pixelY, int levelOfDetail, double *latitude, double *longitude); 40 | void bingPixelXYToTileXY(int pixelX, int pixelY, int *tileX, int *tileY); 41 | void bingTileXYToPixelXY(int tileX, int tileY, int *pixelX, int *pixelY); 42 | void bingTileXYToQuadKey(int tileX, int tileY, int levelOfDetail, char *quadKey); 43 | int bingQuadKeyToTileXY(char *quadKey, int *tileX, int *tileY, int *levelOfDetail); 44 | void bingTileXYToBounds(int tileX, int tileY, int levelOfDetail, double *minLat, double *minLon, double *maxLat, double *maxLon); 45 | int bingQuadKeyToBounds(char *quadkey, double *minLat, double *minLon, double *maxLat, double *maxLon); 46 | void bingLatLongToQuadKey(double latitude, double longitude, int levelOfDetail, char *quadkey); 47 | void bingLatLonToTileXY(double latitude, double longitude, int levelOfDetail, int *tileX, int *tileY); 48 | 49 | 50 | #if defined(__cplusplus) 51 | } 52 | #endif 53 | #endif /* BING_H_ */ 54 | -------------------------------------------------------------------------------- /src/spatial/geom.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016, Josh Baker . 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * * Neither the name of Redis nor the names of its contributors may be used 14 | * to endorse or promote products derived from this software without 15 | * specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 | * THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef GEOM_H_ 31 | #define GEOM_H_ 32 | 33 | #if defined(__cplusplus) 34 | extern "C" { 35 | #endif 36 | 37 | #include 38 | #include 39 | #include "poly.h" 40 | 41 | typedef enum { 42 | GEOM_UNKNOWN = 0, 43 | GEOM_POINT = 1, 44 | GEOM_LINESTRING = 2, 45 | GEOM_POLYGON = 3, 46 | GEOM_MULTIPOINT = 4, 47 | GEOM_MULTILINESTRING = 5, 48 | GEOM_MULTIPOLYGON = 6, 49 | GEOM_GEOMETRYCOLLECTION = 7, 50 | } geomType; 51 | 52 | #define GEOM_VALID_TYPE(t) ((t)>=GEOM_POINT&&(t)<=GEOM_GEOMETRYCOLLECTION) 53 | 54 | typedef enum { 55 | GEOM_ERR_NONE, 56 | GEOM_ERR_UNSUPPORTED, 57 | GEOM_ERR_MEMORY, 58 | GEOM_ERR_INPUT, 59 | } geomErr; 60 | 61 | const char *geomErrText(geomErr err); 62 | 63 | typedef enum { 64 | GEOM_WKT_SHOW_ZM = 1<<0, // Show 'Z' and 'M' for 3D/4D points. 65 | GEOM_WKT_SHOW_EMPTY = 2<<0, // Use 'EMPTY' instead of '()' 66 | } geomWKTEncodeOpts; 67 | 68 | typedef enum { 69 | GEOM_WKT_LEAVE_OPEN = 1<<0, // Used when parsing collections. 70 | GEOM_WKT_REQUIRE_ZM = 1<<1, // 'Z' and 'M' are required for 3D/4D points. 71 | } geomWKTDecodeOpts; 72 | 73 | typedef char *geom; 74 | 75 | typedef struct geomCoord { 76 | double x, y, z, m; 77 | } geomCoord; 78 | 79 | int geomCoordString(geomCoord c, int withZ, int withM, char *str); 80 | 81 | typedef struct geomRect { 82 | geomCoord max, min; 83 | } geomRect; 84 | 85 | geomCoord geomRectCenter(geomRect r); 86 | geomRect geomRectExpand(geomRect r, geomCoord c); 87 | geomRect geomRectUnion(geomRect r1, geomRect r2); 88 | int geomRectString(geomRect r, int withZ, int withM, char *str); 89 | geomErr geomDecodeWKT(const char *input, geomWKTDecodeOpts opts, geom *g, int *size); 90 | geomErr geomDecodeWKB(const void *input, size_t length, geom *g, int *size); 91 | geomErr geomDecode(const void *input, size_t length, geomWKTDecodeOpts opts, geom *g, int *size); 92 | void geomFree(geom g); 93 | 94 | char *geomEncodeWKT(geom g, geomWKTEncodeOpts opts); 95 | void geomFreeWKT(char *wkt); 96 | char *geomEncodeJSON(geom g); 97 | void geomFreeJSON(char *json); 98 | 99 | geomType geomGetType(geom g); 100 | geomCoord geomCenter(geom g); 101 | geomRect geomBounds(geom g); 102 | 103 | typedef struct geomIterator { 104 | int count; 105 | uint8_t *ptr; 106 | geom g; 107 | int sz; 108 | } geomIterator; 109 | 110 | geomErr geomGeometryCollectionIterator(geom g, geomIterator *itr); 111 | int geomIteratorNext(geomIterator *itr); 112 | int geomIteratorValues(geomIterator *itr, geom *g, int *sz); 113 | 114 | geom *geomGeometryCollectionFlattenedArray(geom g, int *count); 115 | void geomFreeFlattenedArray(geom *garr); 116 | geom geomNewCirclePolygon(geomCoord center, double meters, int steps, int *size); 117 | geom geomNewRectPolygon(geomRect rect, int *size); 118 | int geomIsSimplePoint(geom g); 119 | int geomCoordWithinRadius(geomCoord c, geomCoord center, double meters); 120 | 121 | 122 | 123 | /* geomPolyMap is flattened representation of a geometry. 124 | * Each geometry is reduced down to a POINT, LINESTRING, or POLYGON. 125 | * The resulting polygons are stored in the 'polygons' array. 126 | * The number of polygons in the array is specified in 'polygonCount'. 127 | * The 'holes' and 'types' arrays will have 'polygonCount' number of elements. 128 | * 129 | * For example, a simple GEOM_POINT converted to a PolyMap will have 130 | * one element in the 'polygons' array that contains the point data, one 131 | * element in the 'holes' array that is empty, and one element in the 132 | * types array that is GEOM_POINT. 133 | */ 134 | typedef struct geomPolyMap{ 135 | geom g; // first geometry. 136 | geomType type; // type of the first geometry. 137 | int z,m; // indicates that z or m are provided. 138 | int dims; // number of dimensions per point. 139 | int collection; // the geometries are on the heap. 140 | int multipoly; // the polygons and holes are on the heap. 141 | int shared; // this structure does not need to be freed. 142 | 143 | // geoms 144 | int geomCount; // the number of geometries. 145 | geom *geoms; // one or more geometries. 146 | 147 | // polys 148 | polyPoint center; // center point of the first geometry. 149 | polyRect bounds; // minimum bounding rect of the first geometry. 150 | int polygonCount; // number of polygons and holes. 151 | polyPolygon *polygons; // all of the polygons belonging to the geometry. 152 | polyMultiPolygon *holes; // all of the holes belonging to the geometry. 153 | geomType *types; // the geometry type for each polygon/holes. 154 | 155 | // some private vars 156 | polyPolygon ppoly; 157 | polyMultiPolygon pholes; 158 | } geomPolyMap; 159 | 160 | void geomFreePolyMap(geomPolyMap *m); 161 | geomPolyMap *geomNewPolyMap(geom g); 162 | 163 | int geomPolyMapIntersects(geomPolyMap *m1, geomPolyMap *m2); 164 | int geomPolyMapWithin(geomPolyMap *m1, geomPolyMap *m2); 165 | 166 | int geomPolyMapContains(geomPolyMap *m1, geomPolyMap *m2); 167 | int geomPolyMapExIntersects(geomPolyMap *m1, geomPolyMap *m2); 168 | 169 | #if defined(__cplusplus) 170 | } 171 | #endif 172 | #endif /* GEOM_H_ */ 173 | -------------------------------------------------------------------------------- /src/spatial/geom_json.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016, Josh Baker . 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * * Neither the name of Redis nor the names of its contributors may be used 14 | * to endorse or promote products derived from this software without 15 | * specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 | * THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef FROM_GEOM_C 31 | #error This is not a standalone source file. 32 | #endif 33 | 34 | static int jsonDecodeAny(json_value *value, geom *g, int *size); 35 | 36 | static uint8_t *makeBOID(int cap, int id, int dims){ 37 | if (cap < 5){ 38 | return NULL; 39 | } 40 | uint8_t *b = zmalloc(cap); 41 | if (!b){ 42 | return NULL; 43 | } 44 | if (LITTLE_ENDIAN){ 45 | b[0] = 0x01; 46 | } else{ 47 | b[0] = 0x00; 48 | } 49 | switch (dims){ 50 | case 2: 51 | *((uint32_t*)(b+1)) = id; 52 | break; 53 | case 3: 54 | *((uint32_t*)(b+1)) = 1000+id; 55 | break; 56 | case 4: 57 | *((uint32_t*)(b+1)) = 3000+id; 58 | break; 59 | } 60 | return b; 61 | } 62 | 63 | static int jsonGetPosition(json_value *value, int *dims, geomCoord *position){ 64 | if (!value|| 65 | value->type!=json_array|| 66 | value->u.array.length<2|| 67 | value->u.array.length>4 68 | ){ 69 | return 0; 70 | } 71 | geomCoord coord = {0,0,0,0}; 72 | for (int i=0;iu.array.length;i++){ 73 | double n = 0; 74 | json_value *nvalue = value->u.array.values[i]; 75 | if (nvalue->type==json_double){ 76 | n = nvalue->u.dbl; 77 | } else if (nvalue->type==json_integer){ 78 | n = nvalue->u.integer; 79 | } else { 80 | return 0; 81 | } 82 | switch (i){ 83 | case 0: 84 | coord.x = n; 85 | break; 86 | case 1: 87 | coord.y = n; 88 | break; 89 | case 2: 90 | coord.z = n; 91 | break; 92 | case 4: 93 | coord.m = n; 94 | break; 95 | } 96 | } 97 | if (dims) *dims = value->u.array.length; 98 | if (position) *position = coord; 99 | return 1; 100 | } 101 | 102 | static int jsonDecodePoint(json_value *coords, geom *g, int *size){ 103 | if (!coords||coords->type!=json_array){ 104 | return 0; 105 | } 106 | int dims; 107 | geomCoord position; 108 | if (!jsonGetPosition(coords, &dims, &position)){ 109 | return 0; 110 | } 111 | uint8_t *b = makeBOID(5+dims*8, 1, dims); 112 | if (!b){ 113 | return 0; 114 | } 115 | ((double*)(b+5))[0] = position.x; 116 | ((double*)(b+5))[1] = position.y; 117 | if (dims>2){ 118 | ((double*)(b+5))[2] = position.z; 119 | if (dims>3){ 120 | ((double*)(b+5))[3] = position.m; 121 | } 122 | } 123 | if (g) *g = (geom)b; 124 | if (size) *size = 5+dims*8; 125 | return 1; 126 | } 127 | 128 | #define JD_BEGIN(type)\ 129 | int rdims = 0;\ 130 | int cap = 5;\ 131 | uint8_t *b = zmalloc(cap);\ 132 | if (!b){\ 133 | goto err;\ 134 | }\ 135 | if (LITTLE_ENDIAN){\ 136 | b[0] = 0x01;\ 137 | } else{\ 138 | b[0] = 0x00;\ 139 | }\ 140 | *((uint32_t*)(b+1)) = (type);\ 141 | uint8_t *ptr = b+5;\ 142 | rdims = rdims; 143 | 144 | #define JD_EXPAND_BUFFER(n){\ 145 | int ncap = cap+(n);\ 146 | uint8_t *nb = zrealloc(b, ncap);\ 147 | if (!nb){\ 148 | goto err;\ 149 | }\ 150 | ptr = nb+(ptr-b);\ 151 | cap = ncap;\ 152 | b = nb;\ 153 | } 154 | 155 | #define JD_WRITE_INT(n)\ 156 | JD_EXPAND_BUFFER(4);\ 157 | *((uint32_t*)ptr) = (n);\ 158 | ptr+=4; 159 | 160 | #define JD_WRITE_DBL(d)\ 161 | JD_EXPAND_BUFFER(8);\ 162 | *((double*)ptr) = (d);\ 163 | ptr+=8; 164 | 165 | #define JD_END()\ 166 | if (g) *g = (geom)b;\ 167 | if (size) *size = ptr-b;\ 168 | return 1;\ 169 | err:\ 170 | if (b){\ 171 | zfree(b);\ 172 | }\ 173 | return 0; 174 | 175 | 176 | #define JD_WRITE_COORD(value){\ 177 | int dims;\ 178 | geomCoord position;\ 179 | if (!jsonGetPosition((value), &dims, &position)){\ 180 | goto err;\ 181 | }\ 182 | if (rdims==0){\ 183 | rdims=dims;\ 184 | if (rdims!=2){\ 185 | if (rdims==3){\ 186 | *((uint32_t*)(b+1)) += 1000;\ 187 | } else if (rdims==4){\ 188 | *((uint32_t*)(b+1)) += 3000;\ 189 | }\ 190 | }\ 191 | } else if (dims!=rdims){\ 192 | goto err;\ 193 | }\ 194 | JD_WRITE_DBL(position.x);\ 195 | JD_WRITE_DBL(position.y);\ 196 | if (dims>2){\ 197 | JD_WRITE_DBL(position.z);\ 198 | if (dims>3){\ 199 | JD_WRITE_DBL(position.m);\ 200 | }\ 201 | }\ 202 | } 203 | 204 | #define JD_WRITE_MULTI1_COORDS(coords){\ 205 | if (!(coords)||(coords)->type!=json_array){\ 206 | goto err;\ 207 | }\ 208 | JD_WRITE_INT((coords)->u.array.length);\ 209 | for (int i=0;i<(coords)->u.array.length;i++){\ 210 | json_value *nvalue1 = (coords)->u.array.values[i];\ 211 | JD_WRITE_COORD(nvalue1);\ 212 | }\ 213 | } 214 | 215 | #define JD_WRITE_MULTI2_COORDS(coords){\ 216 | if (!(coords)||(coords)->type!=json_array){\ 217 | goto err;\ 218 | }\ 219 | JD_WRITE_INT((coords)->u.array.length);\ 220 | for (int i=0;i<(coords)->u.array.length;i++){\ 221 | json_value *nvalue2 = (coords)->u.array.values[i];\ 222 | JD_WRITE_MULTI1_COORDS(nvalue2);\ 223 | }\ 224 | } 225 | 226 | #define JD_WRITE_MULTI3_COORDS(coords){\ 227 | if (!(coords)||(coords)->type!=json_array){\ 228 | goto err;\ 229 | }\ 230 | JD_WRITE_INT((coords)->u.array.length);\ 231 | for (int i=0;i<(coords)->u.array.length;i++){\ 232 | json_value *nvalue3 = (coords)->u.array.values[i];\ 233 | JD_WRITE_MULTI2_COORDS(nvalue3);\ 234 | }\ 235 | } 236 | 237 | 238 | static int jsonDecodeLineString(json_value *coords, geom *g, int *size){ 239 | JD_BEGIN(2); 240 | JD_WRITE_MULTI1_COORDS(coords); 241 | JD_END(); 242 | } 243 | static int jsonDecodePolygon(json_value *coords, geom *g, int *size){ 244 | JD_BEGIN(3); 245 | JD_WRITE_MULTI2_COORDS(coords); 246 | JD_END(); 247 | } 248 | static int jsonDecodeMultiPoint(json_value *coords, geom *g, int *size){ 249 | JD_BEGIN(4); 250 | JD_WRITE_MULTI1_COORDS(coords); 251 | JD_END(); 252 | } 253 | static int jsonDecodeMultiLineString(json_value *coords, geom *g, int *size){ 254 | JD_BEGIN(5); 255 | JD_WRITE_MULTI2_COORDS(coords); 256 | JD_END(); 257 | } 258 | static int jsonDecodeMultiPolygon(json_value *coords, geom *g, int *size){ 259 | JD_BEGIN(6); 260 | JD_WRITE_MULTI3_COORDS(coords); 261 | JD_END(); 262 | } 263 | 264 | static int jsonDecodeGeometryCollection(json_value *geometries, geom *g, int *size){ 265 | JD_BEGIN(7); 266 | 267 | if (!(geometries)||(geometries)->type!=json_array){ 268 | goto err; 269 | } 270 | JD_WRITE_INT((geometries)->u.array.length); 271 | for (int i=0;i<(geometries)->u.array.length;i++){ 272 | geom ng; 273 | int nsize; 274 | if (!jsonDecodeAny((geometries)->u.array.values[i], &ng, &nsize)){ 275 | goto err; 276 | } 277 | JD_EXPAND_BUFFER(nsize); 278 | memcpy(ptr, (uint8_t*)ng, nsize); 279 | ptr+=nsize; 280 | geomFree(ng); 281 | } 282 | 283 | JD_END(); 284 | } 285 | 286 | static int jsonDecodeAny(json_value *value, geom *g, int *size){ 287 | if (!value|| 288 | value->type != json_object){ 289 | return 0; 290 | } 291 | char *type = ""; 292 | json_value *coordinates = NULL; 293 | json_value *geometries = NULL; 294 | for (int i=0;iu.object.length;i++){ 295 | if (!strcmp(value->u.object.values[i].name, "type")){ 296 | if (value->u.object.values[i].value->type == json_string){ 297 | type = value->u.object.values[i].value->u.string.ptr; 298 | } 299 | } 300 | if (!strcmp(value->u.object.values[i].name, "coordinates")){ 301 | if (value->u.object.values[i].value->type == json_array){ 302 | coordinates = value->u.object.values[i].value; 303 | } 304 | } 305 | if (!strcmp(value->u.object.values[i].name, "geometries")){ 306 | if (value->u.object.values[i].value->type == json_array){ 307 | geometries = value->u.object.values[i].value; 308 | } 309 | } 310 | } 311 | if (!strcmp(type, "Point")){ 312 | return jsonDecodePoint(coordinates, g, size); 313 | } else if (!strcmp(type, "MultiPoint")){ 314 | return jsonDecodeMultiPoint(coordinates, g, size); 315 | } else if (!strcmp(type, "LineString")){ 316 | return jsonDecodeLineString(coordinates, g, size); 317 | } else if (!strcmp(type, "MultiLineString")){ 318 | return jsonDecodeMultiLineString(coordinates, g, size); 319 | } else if (!strcmp(type, "Polygon")){ 320 | return jsonDecodePolygon(coordinates, g, size); 321 | } else if (!strcmp(type, "MultiPolygon")){ 322 | return jsonDecodeMultiPolygon(coordinates, g, size); 323 | } else if (!strcmp(type, "GeometryCollection")){ 324 | return jsonDecodeGeometryCollection(geometries, g, size); 325 | } 326 | return 0; 327 | } 328 | 329 | 330 | 331 | -------------------------------------------------------------------------------- /src/spatial/geom_levels.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016, Josh Baker . 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * * Neither the name of Redis nor the names of its contributors may be used 14 | * to endorse or promote products derived from this software without 15 | * specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 | * THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef FROM_GEOM_C 31 | #error This is not a standalone source file. 32 | #endif 33 | 34 | static geomRect levelAny_geomBounds(uint8_t *g, int *read); 35 | static geomCoord levelAny_geomCenter(uint8_t *g, int *read); 36 | 37 | 38 | static geomRect level1_getBounds(ghdr *h, uint8_t *g, int *read){ 39 | geomRect r; 40 | r.min = readcoord(h, g, read); 41 | r.max = r.min; 42 | return r; 43 | } 44 | 45 | static geomCoord level1_getCenter(ghdr *h, uint8_t *g, int *read){ 46 | return readcoord(h, g, read); 47 | } 48 | 49 | static geomRect level2_getBounds(ghdr *h, uint8_t *g, int *read){ 50 | geomRect r; 51 | memset(&r, 0, sizeof(geomRect)); 52 | int got = 0; 53 | uint8_t *p = g; 54 | int len = *((uint32_t*)p); 55 | int nread = 0; 56 | p += 4; 57 | for (int i=0;i. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * * Neither the name of Redis nor the names of its contributors may be used 14 | * to endorse or promote products derived from this software without 15 | * specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 | * THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef FROM_GEOM_C 31 | #error This is not a standalone source file. 32 | #endif 33 | 34 | 35 | void geomFreePolyMap(geomPolyMap *m){ 36 | if (m) { 37 | if (m->geoms && m->collection){ 38 | geomFreeFlattenedArray(m->geoms); 39 | } 40 | if (m->polygons && m->multipoly){ 41 | zfree(m->polygons); 42 | } 43 | if (m->holes && m->multipoly){ 44 | zfree(m->holes); 45 | } 46 | if (m->types) { 47 | zfree(m->types); 48 | } 49 | zfree(m); 50 | } 51 | } 52 | 53 | static geomPolyMap *geomNewPolyMapBase(geom g){ 54 | if (!g){ 55 | return NULL; 56 | } 57 | uint8_t *ptr = (uint8_t*)g; 58 | if (*ptr!=0&&*ptr!=1){ 59 | return NULL; 60 | } 61 | ptr++; 62 | ghdr h = readhdr(ptr); 63 | ptr+=4; 64 | if (h.type == GEOM_UNKNOWN){ 65 | return NULL; 66 | } 67 | int dims = 2; 68 | if (h.z){ 69 | dims++; 70 | } 71 | if (h.m){ 72 | dims++; 73 | } 74 | geomPolyMap *m = zmalloc(sizeof(geomPolyMap)); 75 | if (!m) { 76 | return NULL; 77 | } 78 | memset(m, 0, sizeof(geomPolyMap)); 79 | 80 | m->g = g; 81 | geomCoord center = geomCenter(g); 82 | m->center.x = center.x; 83 | m->center.y = center.y; 84 | geomRect bounds = geomBounds(g); 85 | m->bounds.min.x = bounds.min.x; 86 | m->bounds.min.y = bounds.min.y; 87 | m->bounds.max.x = bounds.max.x; 88 | m->bounds.max.y = bounds.max.y; 89 | if (h.type==GEOM_GEOMETRYCOLLECTION){ 90 | m->collection = 1; 91 | m->geoms = geomGeometryCollectionFlattenedArray(g, &m->geomCount); 92 | if (!m->geoms){ 93 | goto err; 94 | } 95 | } else { 96 | m->geoms = &g; 97 | m->geomCount = 1; 98 | } 99 | m->type = h.type; 100 | m->z = h.z; 101 | m->m = h.m; 102 | m->dims = dims; 103 | 104 | switch (h.type){ 105 | case GEOM_POINT: 106 | case GEOM_LINESTRING: 107 | switch (h.type){ 108 | case GEOM_POINT: 109 | m->ppoly.len = 1; 110 | break; 111 | case GEOM_LINESTRING: 112 | m->ppoly.len = *((uint32_t*)ptr); 113 | ptr+=4; 114 | break; 115 | } 116 | m->ppoly.dims = dims; 117 | m->ppoly.values = (double*)ptr; 118 | m->polygonCount = 1; 119 | m->polygons = &m->ppoly; 120 | m->holes = &m->pholes; 121 | m->types = zmalloc(sizeof(geomType)); 122 | if (!m->types){ 123 | goto err; 124 | } 125 | *(m->types) = m->type; 126 | break; 127 | case GEOM_POLYGON: 128 | single_polygon:{ 129 | int count = *((uint32_t*)ptr); 130 | ptr+=4; 131 | if (count>0){ 132 | m->ppoly.len = *((uint32_t*)ptr); 133 | ptr+=4; 134 | m->ppoly.dims = dims; 135 | m->ppoly.values = (double*)ptr; 136 | ptr+=dims*8*m->ppoly.len; 137 | if (count>1){ 138 | m->pholes.len = count-1; 139 | m->pholes.dims = dims; 140 | m->pholes.values = (double*)ptr; 141 | } 142 | } 143 | m->polygonCount = 1; 144 | m->polygons = &m->ppoly; 145 | m->holes = &m->pholes; 146 | m->types = zmalloc(sizeof(geomType)); 147 | if (!m->types){ 148 | goto err; 149 | } 150 | *(m->types) = m->type; 151 | break; 152 | } 153 | case GEOM_MULTIPOINT:{ 154 | int count = *((uint32_t*)ptr); 155 | ptr+=4; 156 | m->multipoly = 1; 157 | m->polygonCount = count; 158 | m->polygons = zmalloc(count*sizeof(polyPolygon)); 159 | if (!m->polygons){ 160 | goto err; 161 | } 162 | memset(m->polygons, 0, count*sizeof(polyPolygon)); 163 | m->holes = zmalloc(count*sizeof(polyMultiPolygon)); 164 | if (!m->holes){ 165 | goto err; 166 | } 167 | memset(m->holes, 0, count*sizeof(polyMultiPolygon)); 168 | m->types = zmalloc(count*sizeof(geomType)); 169 | if (!m->types){ 170 | goto err; 171 | } 172 | memset(m->types, 0, count*sizeof(geomType)); 173 | for (int i=0;ipolygons[i].len = 1; 175 | m->polygons[i].dims = dims; 176 | m->polygons[i].values = (double*)ptr; 177 | ptr+=dims*8; 178 | m->types[i] = GEOM_POINT; // reduce to a point 179 | } 180 | break; 181 | } 182 | case GEOM_MULTILINESTRING:{ 183 | int count = *((uint32_t*)ptr); 184 | ptr+=4; 185 | m->multipoly = 1; 186 | m->polygonCount = count; 187 | m->polygons = zmalloc(count*sizeof(polyPolygon)); 188 | if (!m->polygons){ 189 | goto err; 190 | } 191 | memset(m->polygons, 0, count*sizeof(polyPolygon)); 192 | m->holes = zmalloc(count*sizeof(polyMultiPolygon)); 193 | if (!m->holes){ 194 | goto err; 195 | } 196 | memset(m->holes, 0, count*sizeof(polyMultiPolygon)); 197 | m->types = zmalloc(count*sizeof(geomType)); 198 | if (!m->types){ 199 | goto err; 200 | } 201 | memset(m->types, 0, count*sizeof(geomType)); 202 | for (int i=0;ipolygons[i].len = *((uint32_t*)ptr); 204 | ptr+=4; 205 | m->polygons[i].dims = dims; 206 | m->polygons[i].values = (double*)ptr; 207 | ptr+=dims*8*m->polygons[i].len; 208 | m->types[i] = GEOM_LINESTRING; // reduce to a linestring 209 | } 210 | break; 211 | } 212 | case GEOM_MULTIPOLYGON:{ 213 | int count = *((uint32_t*)ptr); 214 | if (count<=1){ 215 | goto single_polygon; 216 | } 217 | ptr+=4; 218 | m->multipoly = 1; 219 | m->polygonCount = count; 220 | m->polygons = zmalloc(count*sizeof(polyPolygon)); 221 | if (!m->polygons){ 222 | goto err; 223 | } 224 | memset(m->polygons, 0, count*sizeof(polyPolygon)); 225 | m->holes = zmalloc(count*sizeof(polyMultiPolygon)); 226 | if (!m->holes){ 227 | goto err; 228 | } 229 | memset(m->holes, 0, count*sizeof(polyMultiPolygon)); 230 | m->types = zmalloc(count*sizeof(geomType)); 231 | if (!m->types){ 232 | goto err; 233 | } 234 | memset(m->types, 0, count*sizeof(geomType)); 235 | for (int i=0;ipolygons[i].len = *((uint32_t*)ptr); 242 | ptr+=4; 243 | m->polygons[i].dims = dims; 244 | m->polygons[i].values = (double*)ptr; 245 | ptr+=dims*8*m->polygons[i].len; 246 | if (count2>1){ 247 | m->holes[i].len = count2-1; 248 | m->holes[i].dims = dims; 249 | m->holes[i].values = (double*)ptr; 250 | for (int j=1;jtypes[i] = GEOM_POLYGON; // reduce to a polygon 257 | } 258 | break; 259 | } 260 | case GEOM_GEOMETRYCOLLECTION:{ 261 | int cap = 1; 262 | int len = 0; 263 | polyPolygon *polygons = NULL; 264 | polyMultiPolygon *holes = NULL; 265 | geomType *types = NULL; 266 | geomPolyMap *m2 = NULL; 267 | polygons = zmalloc(cap*sizeof(polyPolygon)); 268 | if (!polygons){ 269 | goto err2; 270 | } 271 | holes = zmalloc(cap*sizeof(polyMultiPolygon)); 272 | if (!holes){ 273 | goto err2; 274 | } 275 | types = zmalloc(cap*sizeof(geomType)); 276 | if (!types){ 277 | goto err2; 278 | } 279 | for (int i=0;igeomCount;i++){ 280 | geom g2 = m->geoms[i]; 281 | m2 = geomNewPolyMap(g2); 282 | if (!m2){ 283 | goto err2; 284 | } 285 | for (int j=0;jpolygonCount;j++){ 286 | if (len==cap){ 287 | int ncap = cap==0?1:cap*2; 288 | polyPolygon *npolygons = zrealloc(polygons, ncap*sizeof(polyPolygon)); 289 | if (!npolygons){ 290 | goto err2; 291 | } 292 | polyMultiPolygon *nholes = zrealloc(holes, ncap*sizeof(polyMultiPolygon)); 293 | if (!nholes){ 294 | goto err2; 295 | } 296 | geomType *ntypes = zrealloc(types, ncap*sizeof(geomType)); 297 | if (!ntypes){ 298 | goto err2; 299 | } 300 | polygons = npolygons; 301 | holes = nholes; 302 | types = ntypes; 303 | cap = ncap; 304 | } 305 | polygons[len] = m2->polygons[j]; 306 | holes[len] = m2->holes[j]; 307 | types[len] = m2->types[j]; 308 | len++; 309 | } 310 | geomFreePolyMap(m2); 311 | m2 = NULL; 312 | } 313 | m->multipoly = 1; 314 | m->polygonCount = len; 315 | 316 | m->polygons = polygons; 317 | m->holes = holes; 318 | m->types = types; 319 | break; 320 | err2: 321 | if (polygons){ 322 | zfree(polygons); 323 | } 324 | if (holes){ 325 | zfree(holes); 326 | } 327 | if (types){ 328 | zfree(types); 329 | } 330 | if (m2){ 331 | geomFreePolyMap(m2); 332 | } 333 | goto err; 334 | }} 335 | return m; 336 | err: 337 | if (m){ 338 | geomFreePolyMap(m); 339 | } 340 | return NULL; 341 | } 342 | 343 | geomPolyMap *geomNewPolyMap(geom g){ 344 | return geomNewPolyMapBase(g); 345 | } 346 | -------------------------------------------------------------------------------- /src/spatial/geoutil.c: -------------------------------------------------------------------------------- 1 | /* Origin */ 2 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 3 | /* Latitude/longitude spherical geodesy tools (c) Chris Veness 2002-2016 */ 4 | /* MIT Licence */ 5 | /* www.movable-type.co.uk/scripts/latlong.html */ 6 | /* www.movable-type.co.uk/scripts/geodesy/docs/module-latlon-spherical.html */ 7 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 8 | 9 | /* Modified and new stuff */ 10 | /* 11 | * Copyright (c) 2016, Josh Baker . 12 | * All rights reserved. 13 | * 14 | * Redistribution and use in source and binary forms, with or without 15 | * modification, are permitted provided that the following conditions are met: 16 | * 17 | * * Redistributions of source code must retain the above copyright notice, 18 | * this list of conditions and the following disclaimer. 19 | * * Redistributions in binary form must reproduce the above copyright 20 | * notice, this list of conditions and the following disclaimer in the 21 | * documentation and/or other materials provided with the distribution. 22 | * * Neither the name of Redis nor the names of its contributors may be used 23 | * to endorse or promote products derived from this software without 24 | * specific prior written permission. 25 | * 26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 27 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 30 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 36 | * THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | #include 40 | #include 41 | #include "geoutil.h" 42 | 43 | #ifndef PI 44 | #define PI 3.14159265358979323846 45 | #endif 46 | 47 | #define EARTH_RADIUS 6372797.560856 48 | #define RAD(deg) ((deg)*PI/180.0) 49 | #define DEG(rad) ((rad)*180.0/PI) 50 | 51 | double geoutilDistance(double latA, double lonA, double latB, double lonB){ 52 | double q1 = RAD(latA); 53 | double a1 = RAD(lonA); 54 | double q2 = RAD(latB); 55 | double a2 = RAD(lonB); 56 | double aq = q2 - q1; 57 | double av = a2 - a1; 58 | double a = sin(aq/2)*sin(aq/2) + cos(q1)*cos(q2)*sin(av/2)*sin(av/2); 59 | double c = 2 * atan2(sqrt(a), sqrt(1-a)); 60 | return EARTH_RADIUS * c; 61 | } 62 | 63 | void geoutilDestinationLatLon(double lat, double lon, double distanceMeters, double bearingDegrees, double *destLat, double *destLon){ 64 | if (!destLat || !destLon){ 65 | return; 66 | } 67 | double tq = distanceMeters / EARTH_RADIUS; // angular distance in radians 68 | double Z = RAD(bearingDegrees); 69 | double q1 = RAD(lat); 70 | double a1 = RAD(lon); 71 | double q2 = asin(sin(q1)*cos(tq) + cos(q1)*sin(tq)*cos(Z)); 72 | double a2 = a1 + atan2(sin(Z)*sin(tq)*cos(q1), cos(tq)-sin(q1)*sin(q2)); 73 | a2 = fmod(a2+3*PI, 2*PI) - PI; // normalise to -180..+180° 74 | *destLat = DEG(q2); 75 | *destLon = DEG(a2); 76 | } 77 | 78 | geomRect geoutilBoundsFromLatLon(double centerLat, double centerLon, double distanceMeters){ 79 | geomRect r; 80 | memset(&r, 0, sizeof(geomRect)); 81 | double nLat, nLon, eLat, eLon, sLat, sLon, wLat, wLon; 82 | geoutilDestinationLatLon(centerLat, centerLon, distanceMeters, 0, &nLat, &nLon); 83 | geoutilDestinationLatLon(centerLat, centerLon, distanceMeters, 90, &eLat, &eLon); 84 | geoutilDestinationLatLon(centerLat, centerLon, distanceMeters, 180, &sLat, &sLon); 85 | geoutilDestinationLatLon(centerLat, centerLon, distanceMeters, 270, &wLat, &wLon); 86 | r.min.x = wLon; 87 | r.min.y = sLat; 88 | r.max.x = eLon; 89 | r.max.y = nLat; 90 | return r; 91 | } 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/spatial/geoutil.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016, Josh Baker . 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * * Neither the name of Redis nor the names of its contributors may be used 14 | * to endorse or promote products derived from this software without 15 | * specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 | * THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef GEOUTIL_H_ 31 | #define GEOUTIL_H_ 32 | 33 | #if defined(__cplusplus) 34 | extern "C" { 35 | #endif 36 | 37 | #include "geom.h" 38 | 39 | double geoutilDistance(double latA, double lonA, double latB, double lonB); 40 | void geoutilDestinationLatLon(double lat, double lon, double distanceMeters, double bearingDegrees, double *destLat, double *destLon); 41 | geomRect geoutilBoundsFromLatLon(double centerLat, double centerLon, double distanceMeters); 42 | 43 | #if defined(__cplusplus) 44 | } 45 | #endif 46 | 47 | #endif /* GEOUTIL_H_ */ 48 | -------------------------------------------------------------------------------- /src/spatial/geoutil_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "test.h" 4 | #include "geoutil.h" 5 | 6 | static double tdist(double latA, double latB, double lonA, double lonB){ 7 | return floor(geoutilDistance(latA, latB, lonA, lonB)*100)/100; 8 | } 9 | 10 | int test_GeoUtilDistance(){ 11 | assert(tdist(33, -115, 33.5, -115.5)==72476.64); 12 | return 1; 13 | } 14 | 15 | int test_GeoUtilDestination(){ 16 | double lat, lon; 17 | geoutilDestinationLatLon(33, -115, 100000, 90, &lat, &lon); 18 | assert(fabs(lat - 32.995417)<0.00001 && fabs(lon - -113.927719)<0.00001); 19 | return 1; 20 | } 21 | -------------------------------------------------------------------------------- /src/spatial/grisu3.c: -------------------------------------------------------------------------------- 1 | /* This file originated from https://github.com/juj/MathGeoLib 2 | * License: http://www.apache.org/licenses/LICENSE-2.0.html */ 3 | /* This file is part of an implementation of the "grisu3" double to string 4 | conversion algorithm described in the research paper 5 | 6 | "Printing Floating-Point Numbers Quickly And Accurately with Integers" 7 | by Florian Loitsch, available at 8 | http://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf */ 9 | 10 | #include // uint64_t etc. 11 | #include // assert 12 | #include // ceil 13 | #include // sprintf 14 | 15 | #ifdef _MSC_VER 16 | #pragma warning(disable : 4204) // nonstandard extension used : non-constant aggregate initializer 17 | #endif 18 | 19 | #define D64_SIGN 0x8000000000000000ULL 20 | #define D64_EXP_MASK 0x7FF0000000000000ULL 21 | #define D64_FRACT_MASK 0x000FFFFFFFFFFFFFULL 22 | #define D64_IMPLICIT_ONE 0x0010000000000000ULL 23 | #define D64_EXP_POS 52 24 | #define D64_EXP_BIAS 1075 25 | #define DIYFP_FRACT_SIZE 64 26 | #define D_1_LOG2_10 0.30102999566398114 // 1 / lg(10) 27 | #define MIN_TARGET_EXP -60 28 | #define MASK32 0xFFFFFFFFULL 29 | 30 | #define CAST_U64(d) (*(uint64_t*)&d) 31 | #define MIN(x,y) ((x) <= (y) ? (x) : (y)) 32 | #define MAX(x,y) ((x) >= (y) ? (x) : (y)) 33 | 34 | #define MIN_CACHED_EXP -348 35 | #define CACHED_EXP_STEP 8 36 | 37 | typedef struct diy_fp 38 | { 39 | uint64_t f; 40 | int e; 41 | } diy_fp; 42 | 43 | typedef struct power 44 | { 45 | uint64_t fract; 46 | int16_t b_exp, d_exp; 47 | } power; 48 | 49 | static const power pow_cache[] = 50 | { 51 | { 0xfa8fd5a0081c0288ULL, -1220, -348 }, 52 | { 0xbaaee17fa23ebf76ULL, -1193, -340 }, 53 | { 0x8b16fb203055ac76ULL, -1166, -332 }, 54 | { 0xcf42894a5dce35eaULL, -1140, -324 }, 55 | { 0x9a6bb0aa55653b2dULL, -1113, -316 }, 56 | { 0xe61acf033d1a45dfULL, -1087, -308 }, 57 | { 0xab70fe17c79ac6caULL, -1060, -300 }, 58 | { 0xff77b1fcbebcdc4fULL, -1034, -292 }, 59 | { 0xbe5691ef416bd60cULL, -1007, -284 }, 60 | { 0x8dd01fad907ffc3cULL, -980, -276 }, 61 | { 0xd3515c2831559a83ULL, -954, -268 }, 62 | { 0x9d71ac8fada6c9b5ULL, -927, -260 }, 63 | { 0xea9c227723ee8bcbULL, -901, -252 }, 64 | { 0xaecc49914078536dULL, -874, -244 }, 65 | { 0x823c12795db6ce57ULL, -847, -236 }, 66 | { 0xc21094364dfb5637ULL, -821, -228 }, 67 | { 0x9096ea6f3848984fULL, -794, -220 }, 68 | { 0xd77485cb25823ac7ULL, -768, -212 }, 69 | { 0xa086cfcd97bf97f4ULL, -741, -204 }, 70 | { 0xef340a98172aace5ULL, -715, -196 }, 71 | { 0xb23867fb2a35b28eULL, -688, -188 }, 72 | { 0x84c8d4dfd2c63f3bULL, -661, -180 }, 73 | { 0xc5dd44271ad3cdbaULL, -635, -172 }, 74 | { 0x936b9fcebb25c996ULL, -608, -164 }, 75 | { 0xdbac6c247d62a584ULL, -582, -156 }, 76 | { 0xa3ab66580d5fdaf6ULL, -555, -148 }, 77 | { 0xf3e2f893dec3f126ULL, -529, -140 }, 78 | { 0xb5b5ada8aaff80b8ULL, -502, -132 }, 79 | { 0x87625f056c7c4a8bULL, -475, -124 }, 80 | { 0xc9bcff6034c13053ULL, -449, -116 }, 81 | { 0x964e858c91ba2655ULL, -422, -108 }, 82 | { 0xdff9772470297ebdULL, -396, -100 }, 83 | { 0xa6dfbd9fb8e5b88fULL, -369, -92 }, 84 | { 0xf8a95fcf88747d94ULL, -343, -84 }, 85 | { 0xb94470938fa89bcfULL, -316, -76 }, 86 | { 0x8a08f0f8bf0f156bULL, -289, -68 }, 87 | { 0xcdb02555653131b6ULL, -263, -60 }, 88 | { 0x993fe2c6d07b7facULL, -236, -52 }, 89 | { 0xe45c10c42a2b3b06ULL, -210, -44 }, 90 | { 0xaa242499697392d3ULL, -183, -36 }, 91 | { 0xfd87b5f28300ca0eULL, -157, -28 }, 92 | { 0xbce5086492111aebULL, -130, -20 }, 93 | { 0x8cbccc096f5088ccULL, -103, -12 }, 94 | { 0xd1b71758e219652cULL, -77, -4 }, 95 | { 0x9c40000000000000ULL, -50, 4 }, 96 | { 0xe8d4a51000000000ULL, -24, 12 }, 97 | { 0xad78ebc5ac620000ULL, 3, 20 }, 98 | { 0x813f3978f8940984ULL, 30, 28 }, 99 | { 0xc097ce7bc90715b3ULL, 56, 36 }, 100 | { 0x8f7e32ce7bea5c70ULL, 83, 44 }, 101 | { 0xd5d238a4abe98068ULL, 109, 52 }, 102 | { 0x9f4f2726179a2245ULL, 136, 60 }, 103 | { 0xed63a231d4c4fb27ULL, 162, 68 }, 104 | { 0xb0de65388cc8ada8ULL, 189, 76 }, 105 | { 0x83c7088e1aab65dbULL, 216, 84 }, 106 | { 0xc45d1df942711d9aULL, 242, 92 }, 107 | { 0x924d692ca61be758ULL, 269, 100 }, 108 | { 0xda01ee641a708deaULL, 295, 108 }, 109 | { 0xa26da3999aef774aULL, 322, 116 }, 110 | { 0xf209787bb47d6b85ULL, 348, 124 }, 111 | { 0xb454e4a179dd1877ULL, 375, 132 }, 112 | { 0x865b86925b9bc5c2ULL, 402, 140 }, 113 | { 0xc83553c5c8965d3dULL, 428, 148 }, 114 | { 0x952ab45cfa97a0b3ULL, 455, 156 }, 115 | { 0xde469fbd99a05fe3ULL, 481, 164 }, 116 | { 0xa59bc234db398c25ULL, 508, 172 }, 117 | { 0xf6c69a72a3989f5cULL, 534, 180 }, 118 | { 0xb7dcbf5354e9beceULL, 561, 188 }, 119 | { 0x88fcf317f22241e2ULL, 588, 196 }, 120 | { 0xcc20ce9bd35c78a5ULL, 614, 204 }, 121 | { 0x98165af37b2153dfULL, 641, 212 }, 122 | { 0xe2a0b5dc971f303aULL, 667, 220 }, 123 | { 0xa8d9d1535ce3b396ULL, 694, 228 }, 124 | { 0xfb9b7cd9a4a7443cULL, 720, 236 }, 125 | { 0xbb764c4ca7a44410ULL, 747, 244 }, 126 | { 0x8bab8eefb6409c1aULL, 774, 252 }, 127 | { 0xd01fef10a657842cULL, 800, 260 }, 128 | { 0x9b10a4e5e9913129ULL, 827, 268 }, 129 | { 0xe7109bfba19c0c9dULL, 853, 276 }, 130 | { 0xac2820d9623bf429ULL, 880, 284 }, 131 | { 0x80444b5e7aa7cf85ULL, 907, 292 }, 132 | { 0xbf21e44003acdd2dULL, 933, 300 }, 133 | { 0x8e679c2f5e44ff8fULL, 960, 308 }, 134 | { 0xd433179d9c8cb841ULL, 986, 316 }, 135 | { 0x9e19db92b4e31ba9ULL, 1013, 324 }, 136 | { 0xeb96bf6ebadf77d9ULL, 1039, 332 }, 137 | { 0xaf87023b9bf0ee6bULL, 1066, 340 } 138 | }; 139 | 140 | static int cached_pow(int exp, diy_fp *p) 141 | { 142 | int k = (int)ceil((exp+DIYFP_FRACT_SIZE-1) * D_1_LOG2_10); 143 | int i = (k-MIN_CACHED_EXP-1) / CACHED_EXP_STEP + 1; 144 | p->f = pow_cache[i].fract; 145 | p->e = pow_cache[i].b_exp; 146 | return pow_cache[i].d_exp; 147 | } 148 | 149 | static diy_fp minus(diy_fp x, diy_fp y) 150 | { 151 | diy_fp d; d.f = x.f - y.f; d.e = x.e; 152 | assert(x.e == y.e && x.f >= y.f); 153 | return d; 154 | } 155 | 156 | static diy_fp multiply(diy_fp x, diy_fp y) 157 | { 158 | uint64_t a, b, c, d, ac, bc, ad, bd, tmp; 159 | diy_fp r; 160 | a = x.f >> 32; b = x.f & MASK32; 161 | c = y.f >> 32; d = y.f & MASK32; 162 | ac = a*c; bc = b*c; 163 | ad = a*d; bd = b*d; 164 | tmp = (bd >> 32) + (ad & MASK32) + (bc & MASK32); 165 | tmp += 1U << 31; // round 166 | r.f = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32); 167 | r.e = x.e + y.e + 64; 168 | return r; 169 | } 170 | 171 | static diy_fp normalize_diy_fp(diy_fp n) 172 | { 173 | assert(n.f != 0); 174 | while(!(n.f & 0xFFC0000000000000ULL)) { n.f <<= 10; n.e -= 10; } 175 | while(!(n.f & D64_SIGN)) { n.f <<= 1; --n.e; } 176 | return n; 177 | } 178 | 179 | static diy_fp double2diy_fp(double d) 180 | { 181 | diy_fp fp; 182 | uint64_t u64 = CAST_U64(d); 183 | if (!(u64 & D64_EXP_MASK)) { fp.f = u64 & D64_FRACT_MASK; fp.e = 1 - D64_EXP_BIAS; } 184 | else { fp.f = (u64 & D64_FRACT_MASK) + D64_IMPLICIT_ONE; fp.e = (int)((u64 & D64_EXP_MASK) >> D64_EXP_POS) - D64_EXP_BIAS; } 185 | return fp; 186 | } 187 | 188 | // pow10_cache[i] = 10^(i-1) 189 | static const unsigned int pow10_cache[] = { 0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; 190 | 191 | static int largest_pow10(uint32_t n, int n_bits, uint32_t *power) 192 | { 193 | int guess = ((n_bits + 1) * 1233 >> 12) + 1/*skip first entry*/; 194 | if (n < pow10_cache[guess]) --guess; // We don't have any guarantees that 2^n_bits <= n. 195 | *power = pow10_cache[guess]; 196 | return guess; 197 | } 198 | 199 | static int round_weed(char *buffer, int len, uint64_t wp_W, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t ulp) 200 | { 201 | uint64_t wp_Wup = wp_W - ulp; 202 | uint64_t wp_Wdown = wp_W + ulp; 203 | while(rest < wp_Wup && delta - rest >= ten_kappa 204 | && (rest + ten_kappa < wp_Wup || wp_Wup - rest >= rest + ten_kappa - wp_Wup)) 205 | { 206 | --buffer[len-1]; 207 | rest += ten_kappa; 208 | } 209 | if (rest < wp_Wdown && delta - rest >= ten_kappa 210 | && (rest + ten_kappa < wp_Wdown || wp_Wdown - rest > rest + ten_kappa - wp_Wdown)) 211 | return 0; 212 | 213 | return 2*ulp <= rest && rest <= delta - 4*ulp; 214 | } 215 | 216 | static int digit_gen(diy_fp low, diy_fp w, diy_fp high, char *buffer, int *length, int *kappa) 217 | { 218 | uint64_t unit = 1; 219 | diy_fp too_low = { low.f - unit, low.e }; 220 | diy_fp too_high = { high.f + unit, high.e }; 221 | diy_fp unsafe_interval = minus(too_high, too_low); 222 | diy_fp one = { 1ULL << -w.e, w.e }; 223 | uint32_t p1 = (uint32_t)(too_high.f >> -one.e); 224 | uint64_t p2 = too_high.f & (one.f - 1); 225 | uint32_t div; 226 | *kappa = largest_pow10(p1, DIYFP_FRACT_SIZE + one.e, &div); 227 | *length = 0; 228 | 229 | while(*kappa > 0) 230 | { 231 | uint64_t rest; 232 | int digit = p1 / div; 233 | buffer[*length] = (char)('0' + digit); 234 | ++*length; 235 | p1 %= div; 236 | --*kappa; 237 | rest = ((uint64_t)p1 << -one.e) + p2; 238 | if (rest < unsafe_interval.f) return round_weed(buffer, *length, minus(too_high, w).f, unsafe_interval.f, rest, (uint64_t)div << -one.e, unit); 239 | div /= 10; 240 | } 241 | 242 | for(;;) 243 | { 244 | int digit; 245 | p2 *= 10; 246 | unit *= 10; 247 | unsafe_interval.f *= 10; 248 | // Integer division by one. 249 | digit = (int)(p2 >> -one.e); 250 | buffer[*length] = (char)('0' + digit); 251 | ++*length; 252 | p2 &= one.f - 1; // Modulo by one. 253 | --*kappa; 254 | if (p2 < unsafe_interval.f) return round_weed(buffer, *length, minus(too_high, w).f * unit, unsafe_interval.f, p2, one.f, unit); 255 | } 256 | } 257 | 258 | static int grisu3(double v, char *buffer, int *length, int *d_exp) 259 | { 260 | int mk, kappa, success; 261 | diy_fp dfp = double2diy_fp(v); 262 | diy_fp w = normalize_diy_fp(dfp); 263 | 264 | // normalize boundaries 265 | diy_fp t = { (dfp.f << 1) + 1, dfp.e - 1 }; 266 | diy_fp b_plus = normalize_diy_fp(t); 267 | diy_fp b_minus; 268 | diy_fp c_mk; // Cached power of ten: 10^-k 269 | uint64_t u64 = CAST_U64(v); 270 | assert(v > 0 && v <= 1.7976931348623157e308); // Grisu only handles strictly positive finite numbers. 271 | if (!(u64 & D64_FRACT_MASK) && (u64 & D64_EXP_MASK) != 0) { b_minus.f = (dfp.f << 2) - 1; b_minus.e = dfp.e - 2;} // lower boundary is closer? 272 | else { b_minus.f = (dfp.f << 1) - 1; b_minus.e = dfp.e - 1; } 273 | b_minus.f = b_minus.f << (b_minus.e - b_plus.e); 274 | b_minus.e = b_plus.e; 275 | 276 | mk = cached_pow(MIN_TARGET_EXP - DIYFP_FRACT_SIZE - w.e, &c_mk); 277 | 278 | w = multiply(w, c_mk); 279 | b_minus = multiply(b_minus, c_mk); 280 | b_plus = multiply(b_plus, c_mk); 281 | 282 | success = digit_gen(b_minus, w, b_plus, buffer, length, &kappa); 283 | *d_exp = kappa - mk; 284 | return success; 285 | } 286 | 287 | static int i_to_str(int val, char *str) 288 | { 289 | int len, i; 290 | char *s; 291 | char *begin = str; 292 | if (val < 0) { *str++ = '-'; val = -val; } 293 | s = str; 294 | 295 | for(;;) 296 | { 297 | int ni = val / 10; 298 | int digit = val - ni*10; 299 | *s++ = (char)('0' + digit); 300 | if (ni == 0) 301 | break; 302 | val = ni; 303 | } 304 | *s = '\0'; 305 | len = (int)(s - str); 306 | for(i = 0; i < len/2; ++i) 307 | { 308 | char ch = str[i]; 309 | str[i] = str[len-1-i]; 310 | str[len-1-i] = ch; 311 | } 312 | 313 | return (int)(s - begin); 314 | } 315 | 316 | int dtoa_grisu3(double v, char *dst) 317 | { 318 | int d_exp, len, success, decimals, i; 319 | uint64_t u64 = CAST_U64(v); 320 | char *s2 = dst; 321 | assert(dst); 322 | 323 | // Prehandle NaNs 324 | if ((u64 << 1) > 0xFFE0000000000000ULL) return sprintf(dst, "NaN(%08X%08X)", (uint32_t)(u64 >> 32), (uint32_t)u64); 325 | // Prehandle negative values. 326 | if ((u64 & D64_SIGN) != 0) { *s2++ = '-'; v = -v; u64 ^= D64_SIGN; } 327 | // Prehandle zero. 328 | if (!u64) { *s2++ = '0'; *s2 = '\0'; return (int)(s2 - dst); } 329 | // Prehandle infinity. 330 | if (u64 == D64_EXP_MASK) { *s2++ = 'i'; *s2++ = 'n'; *s2++ = 'f'; *s2 = '\0'; return (int)(s2 - dst); } 331 | 332 | success = grisu3(v, s2, &len, &d_exp); 333 | // If grisu3 was not able to convert the number to a string, then use old sprintf (suboptimal). 334 | if (!success) return sprintf(s2, "%.17g", v) + (int)(s2 - dst); 335 | 336 | // We now have an integer string of form "151324135" and a base-10 exponent for that number. 337 | // Next, decide the best presentation for that string by whether to use a decimal point, or the scientific exponent notation 'e'. 338 | // We don't pick the absolute shortest representation, but pick a balance between readability and shortness, e.g. 339 | // 1.545056189557677e-308 could be represented in a shorter form 340 | // 1545056189557677e-323 but that would be somewhat unreadable. 341 | decimals = MIN(-d_exp, MAX(1, len-1)); 342 | if (d_exp < 0 && len > 1) // Add decimal point? 343 | { 344 | for(i = 0; i < decimals; ++i) s2[len-i] = s2[len-i-1]; 345 | s2[len++ - decimals] = '.'; 346 | d_exp += decimals; 347 | // Need scientific notation as well? 348 | if (d_exp != 0) { s2[len++] = 'e'; len += i_to_str(d_exp, s2+len); } 349 | } 350 | else if (d_exp < 0 && d_exp >= -3) // Add decimal point for numbers of form 0.000x where it's shorter? 351 | { 352 | for(i = 0; i < len; ++i) s2[len-d_exp-1-i] = s2[len-i-1]; 353 | s2[0] = '.'; 354 | for(i = 1; i < -d_exp; ++i) s2[i] = '0'; 355 | len += -d_exp; 356 | } 357 | // Add scientific notation? 358 | else if (d_exp < 0 || d_exp > 2) { s2[len++] = 'e'; len += i_to_str(d_exp, s2+len); } 359 | // Add zeroes instead of scientific notation? 360 | else if (d_exp > 0) { while(d_exp-- > 0) s2[len++] = '0'; } 361 | s2[len] = '\0'; // grisu3 doesn't null terminate, so ensure termination. 362 | return (int)(s2+len-dst); 363 | } -------------------------------------------------------------------------------- /src/spatial/grisu3.h: -------------------------------------------------------------------------------- 1 | /* This file originated from https://github.com/juj/MathGeoLib 2 | * License: http://www.apache.org/licenses/LICENSE-2.0.html */ 3 | /* This file is part of an implementation of the "grisu3" double to string 4 | conversion algorithm described in the research paper 5 | "Printing Floating-Point Numbers Quickly And Accurately with Integers" 6 | by Florian Loitsch, available at 7 | http://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf */ 8 | 9 | #ifndef GRISU3_H_ 10 | #define GRISU3_H_ 11 | 12 | #if defined(__cplusplus) 13 | extern "C" { 14 | #endif 15 | 16 | /// Converts the given double-precision floating point number to a string representation. 17 | /** For most inputs, this string representation is the 18 | shortest such, which deserialized again, returns the same bit 19 | representation of the double. 20 | @param v The number to convert. 21 | @param dst [out] The double-precision floating point number will be written here 22 | as a null-terminated string. The conversion algorithm will write at most 25 bytes 23 | to this buffer. (null terminator is included in this count). 24 | The dst pointer may not be null. 25 | @return the number of characters written to dst, excluding the null terminator (which 26 | is always written) is returned here. */ 27 | int dtoa_grisu3(double v, char *dst); 28 | 29 | #if defined(__cplusplus) 30 | } 31 | #endif 32 | #endif /* GEOM_H_ */ -------------------------------------------------------------------------------- /src/spatial/hash.c: -------------------------------------------------------------------------------- 1 | /* Geohash implementation ported from Javascript to C */ 2 | 3 | /* Javascript copyright 4 | * 5 | * Geohash encoding/decoding and associated functions 6 | * (c) Chris Veness 2014 / MIT Licence 7 | */ 8 | 9 | /* C copyright 10 | * 11 | * Copyright (c) 2016, Josh Baker . 12 | * All rights reserved. 13 | * 14 | * Redistribution and use in source and binary forms, with or without 15 | * modification, are permitted provided that the following conditions are met: 16 | * 17 | * * Redistributions of source code must retain the above copyright notice, 18 | * this list of conditions and the following disclaimer. 19 | * * Redistributions in binary form must reproduce the above copyright 20 | * notice, this list of conditions and the following disclaimer in the 21 | * documentation and/or other materials provided with the distribution. 22 | * * Neither the name of Redis nor the names of its contributors may be used 23 | * to endorse or promote products derived from this software without 24 | * specific prior written permission. 25 | * 26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 27 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 30 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 36 | * THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | #include "hash.h" 40 | 41 | static inline int base32R(char b); 42 | static inline char base32F(int i); 43 | 44 | int hashEncode(double lat, double lon, int precision, char *hash){ 45 | int idx = 0; // index into base32 map 46 | int bit = 0; // each char holds 5 bits 47 | int evenBit = 1; 48 | int glen = 0; 49 | double latMin = -90.0; 50 | double latMax = 90.0; 51 | double lonMin = -180.0; 52 | double lonMax = 180.0; 53 | if (precision < 1 || !hash) { 54 | return 0; 55 | } 56 | 57 | while (glen < precision) { 58 | if (evenBit) { 59 | // bisect E-W longitude 60 | double lonMid = (lonMin + lonMax) / 2; 61 | if (lon > lonMid) { 62 | idx = idx*2 + 1; 63 | lonMin = lonMid; 64 | } else { 65 | idx = idx * 2; 66 | lonMax = lonMid; 67 | } 68 | } else { 69 | // bisect N-S latitude 70 | double latMid = (latMin + latMax) / 2; 71 | if (lat > latMid) { 72 | idx = idx*2 + 1; 73 | latMin = latMid; 74 | } else { 75 | idx = idx * 2; 76 | latMax = latMid; 77 | } 78 | } 79 | evenBit = evenBit?0:1; 80 | 81 | bit = bit + 1; 82 | if (bit == 5) { 83 | // 5 bits gives us a character: append it and start over 84 | char b = base32F(idx); 85 | if (!b) { 86 | return 0; 87 | } 88 | hash[glen++] = b; 89 | bit = 0; 90 | idx = 0; 91 | } 92 | } 93 | hash[glen] = '\0'; 94 | return glen; 95 | } 96 | 97 | int hashBounds(char *hash, double *swLat, double *swLon, double *neLat, double *neLon){ 98 | int evenBit = 1; 99 | double latMin = -90.0; 100 | double latMax = 90.0; 101 | double lonMin = -180.0; 102 | double lonMax = 180.0; 103 | char *p = hash; 104 | while (*p) { 105 | char chr = *p; 106 | int idx = base32R(chr); 107 | if (idx == -1) { 108 | return 0; 109 | } 110 | for (int n=4;n>=0;n--) { 111 | int bitN = idx >> n & 1; 112 | if (evenBit) { 113 | // longitude 114 | double lonMid = (lonMin + lonMax) / 2; 115 | if (bitN == 1) { 116 | lonMin = lonMid; 117 | } else { 118 | lonMax = lonMid; 119 | } 120 | } else { 121 | // latitude 122 | double latMid = (latMin + latMax) / 2; 123 | if (bitN == 1) { 124 | latMin = latMid; 125 | } else { 126 | latMax = latMid; 127 | } 128 | } 129 | evenBit = evenBit?0:1; 130 | if (n == 0) { 131 | break; 132 | } 133 | } 134 | p++; 135 | } 136 | if (swLat) *swLat = latMin; 137 | if (swLon) *swLon = lonMin; 138 | if (neLat) *neLat = latMax; 139 | if (neLon) *neLon = lonMax; 140 | return 1; 141 | } 142 | 143 | int hashDecode(char *hash, double *lat, double *lon) { 144 | if (!hash){ 145 | return 0; 146 | } 147 | double swLat, swLon, neLat, neLon; 148 | if (!hashBounds(hash, &swLat, &swLon, &neLat, &neLon)){ 149 | return 0; 150 | } 151 | if (lat) *lat = (neLat-swLat)/2 + swLat; 152 | if (lon) *lon = (neLon-swLon)/2 + swLon; 153 | return 1; 154 | } 155 | 156 | static inline int base32R(char b) { 157 | switch (b) { 158 | default:return -1; 159 | case '0':return 0; 160 | case '1':return 1; 161 | case '2':return 2; 162 | case '3':return 3; 163 | case '4':return 4; 164 | case '5':return 5; 165 | case '6':return 6; 166 | case '7':return 7; 167 | case '8':return 8; 168 | case '9':return 9; 169 | case 'b':case 'B':return 10; 170 | case 'c':case 'C':return 11; 171 | case 'd':case 'D':return 12; 172 | case 'e':case 'E':return 13; 173 | case 'f':case 'F':return 14; 174 | case 'g':case 'G':return 15; 175 | case 'h':case 'H':return 16; 176 | case 'j':case 'J':return 17; 177 | case 'k':case 'K':return 18; 178 | case 'm':case 'M':return 19; 179 | case 'n':case 'N':return 20; 180 | case 'p':case 'P':return 21; 181 | case 'q':case 'Q':return 22; 182 | case 'r':case 'R':return 23; 183 | case 's':case 'S':return 24; 184 | case 't':case 'T':return 25; 185 | case 'u':case 'U':return 26; 186 | case 'v':case 'V':return 27; 187 | case 'w':case 'W':return 28; 188 | case 'x':case 'X':return 29; 189 | case 'y':case 'Y':return 30; 190 | case 'z':case 'Z':return 31; 191 | } 192 | } 193 | 194 | static char base32[32] = "0123456789bcdefghjkmnpqrstuvwxyz"; 195 | 196 | static inline char base32F(int i) { 197 | if (i<0||i>31){ 198 | return 0; 199 | } 200 | return base32[i]; 201 | } 202 | 203 | 204 | -------------------------------------------------------------------------------- /src/spatial/hash.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016, Josh Baker 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of Redis nor the names of its contributors may be used 13 | * to endorse or promote products derived from this software without 14 | * specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED by THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #ifndef HASH_H_ 30 | #define HASH_H_ 31 | 32 | #if defined(__cplusplus) 33 | extern "C" { 34 | #endif 35 | 36 | /* hashEncode creates a geohash from the provided lat,lon. 37 | * Make sure that the hash has enough room to fit the precision. 38 | * The return value is zero for error or the number of 39 | * bytes written to the hash string. */ 40 | int hashEncode(double lat, double lon, int precision, char *hash); 41 | 42 | /* hashDecode decodes a geohash and puts the results into lat,lon. 43 | * The return value is zero for error or one for success. 44 | */ 45 | int hashDecode(char *hash, double *lat, double *lon); 46 | 47 | 48 | /* hashBounds decodes a geohash into bounds and puts the results 49 | * into swlat,swlon,nelat,nelon. 50 | * The return value is zero for error or one for success. 51 | */ 52 | int hashBounds(char *hash, double *swLat, double *swLon, double *neLat, double *neLon); 53 | 54 | 55 | #if defined(__cplusplus) 56 | } 57 | #endif 58 | 59 | #endif /* HASH_H_ */ 60 | -------------------------------------------------------------------------------- /src/spatial/json.h: -------------------------------------------------------------------------------- 1 | 2 | /* vim: set et ts=3 sw=3 sts=3 ft=c: 3 | * 4 | * Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved. 5 | * https://github.com/udp/json-parser 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef _JSON_H 32 | #define _JSON_H 33 | 34 | #ifndef json_char 35 | #define json_char char 36 | #endif 37 | 38 | #ifndef json_int_t 39 | #ifndef _MSC_VER 40 | #include 41 | #define json_int_t int64_t 42 | #else 43 | #define json_int_t __int64 44 | #endif 45 | #endif 46 | 47 | #include 48 | 49 | #ifdef __cplusplus 50 | 51 | #include 52 | 53 | extern "C" 54 | { 55 | 56 | #endif 57 | 58 | typedef struct 59 | { 60 | unsigned long max_memory; 61 | int settings; 62 | 63 | /* Custom allocator support (leave null to use malloc/free) 64 | */ 65 | 66 | void * (* mem_alloc) (size_t, int zero, void * user_data); 67 | void (* mem_free) (void *, void * user_data); 68 | 69 | void * user_data; /* will be passed to mem_alloc and mem_free */ 70 | 71 | size_t value_extra; /* how much extra space to allocate for values? */ 72 | 73 | } json_settings; 74 | 75 | #define json_enable_comments 0x01 76 | 77 | typedef enum 78 | { 79 | json_none, 80 | json_object, 81 | json_array, 82 | json_integer, 83 | json_double, 84 | json_string, 85 | json_boolean, 86 | json_null 87 | 88 | } json_type; 89 | 90 | extern const struct _json_value json_value_none; 91 | 92 | typedef struct _json_object_entry 93 | { 94 | json_char * name; 95 | unsigned int name_length; 96 | 97 | struct _json_value * value; 98 | 99 | } json_object_entry; 100 | 101 | typedef struct _json_value 102 | { 103 | struct _json_value * parent; 104 | 105 | json_type type; 106 | 107 | union 108 | { 109 | int boolean; 110 | json_int_t integer; 111 | double dbl; 112 | 113 | struct 114 | { 115 | unsigned int length; 116 | json_char * ptr; /* null terminated */ 117 | 118 | } string; 119 | 120 | struct 121 | { 122 | unsigned int length; 123 | 124 | json_object_entry * values; 125 | 126 | #if defined(__cplusplus) && __cplusplus >= 201103L 127 | decltype(values) begin () const 128 | { return values; 129 | } 130 | decltype(values) end () const 131 | { return values + length; 132 | } 133 | #endif 134 | 135 | } object; 136 | 137 | struct 138 | { 139 | unsigned int length; 140 | struct _json_value ** values; 141 | 142 | #if defined(__cplusplus) && __cplusplus >= 201103L 143 | decltype(values) begin () const 144 | { return values; 145 | } 146 | decltype(values) end () const 147 | { return values + length; 148 | } 149 | #endif 150 | 151 | } array; 152 | 153 | } u; 154 | 155 | union 156 | { 157 | struct _json_value * next_alloc; 158 | void * object_mem; 159 | 160 | } _reserved; 161 | 162 | #ifdef JSON_TRACK_SOURCE 163 | 164 | /* Location of the value in the source JSON 165 | */ 166 | unsigned int line, col; 167 | 168 | #endif 169 | 170 | 171 | /* Some C++ operator sugar */ 172 | 173 | #ifdef __cplusplus 174 | 175 | public: 176 | 177 | inline _json_value () 178 | { memset (this, 0, sizeof (_json_value)); 179 | } 180 | 181 | inline const struct _json_value &operator [] (int index) const 182 | { 183 | if (type != json_array || index < 0 184 | || ((unsigned int) index) >= u.array.length) 185 | { 186 | return json_value_none; 187 | } 188 | 189 | return *u.array.values [index]; 190 | } 191 | 192 | inline const struct _json_value &operator [] (const char * index) const 193 | { 194 | if (type != json_object) 195 | return json_value_none; 196 | 197 | for (unsigned int i = 0; i < u.object.length; ++ i) 198 | if (!strcmp (u.object.values [i].name, index)) 199 | return *u.object.values [i].value; 200 | 201 | return json_value_none; 202 | } 203 | 204 | inline operator const char * () const 205 | { 206 | switch (type) 207 | { 208 | case json_string: 209 | return u.string.ptr; 210 | 211 | default: 212 | return ""; 213 | }; 214 | } 215 | 216 | inline operator json_int_t () const 217 | { 218 | switch (type) 219 | { 220 | case json_integer: 221 | return u.integer; 222 | 223 | case json_double: 224 | return (json_int_t) u.dbl; 225 | 226 | default: 227 | return 0; 228 | }; 229 | } 230 | 231 | inline operator bool () const 232 | { 233 | if (type != json_boolean) 234 | return false; 235 | 236 | return u.boolean != 0; 237 | } 238 | 239 | inline operator double () const 240 | { 241 | switch (type) 242 | { 243 | case json_integer: 244 | return (double) u.integer; 245 | 246 | case json_double: 247 | return u.dbl; 248 | 249 | default: 250 | return 0; 251 | }; 252 | } 253 | 254 | #endif 255 | 256 | } json_value; 257 | 258 | json_value * json_parse (const json_char * json, 259 | size_t length); 260 | 261 | #define json_error_max 128 262 | json_value * json_parse_ex (json_settings * settings, 263 | const json_char * json, 264 | size_t length, 265 | char * error); 266 | 267 | void json_value_free (json_value *); 268 | 269 | 270 | /* Not usually necessary, unless you used a custom mem_alloc and now want to 271 | * use a custom mem_free. 272 | */ 273 | void json_value_free_ex (json_settings * settings, 274 | json_value *); 275 | 276 | 277 | #ifdef __cplusplus 278 | } /* extern "C" */ 279 | #endif 280 | 281 | #endif 282 | 283 | -------------------------------------------------------------------------------- /src/spatial/poly.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016, Josh Baker 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of Redis nor the names of its contributors may be used 13 | * to endorse or promote products derived from this software without 14 | * specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED by THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include "zmalloc.h" 34 | #include "poly.h" 35 | #include "grisu3.h" 36 | 37 | // InsideRect detects point is inside of another rect 38 | int polyPointInsideRect(polyPoint p, polyRect r) { 39 | if (p.x < r.min.x || p.x > r.max.x) { 40 | return 0; 41 | } 42 | if (p.y < r.min.y || p.y > r.max.y) { 43 | return 0; 44 | } 45 | return 1; 46 | } 47 | 48 | polyPolygon polyPolygonFromGeomSegment(void *segment, int dims){ 49 | polyPolygon pp; 50 | pp.len = (int)((uint32_t*)segment)[0]; 51 | pp.dims = dims; 52 | pp.values = (double*)(((uint8_t*)segment)+4); 53 | return pp; 54 | } 55 | 56 | polyPoint polyPolygonPoint(polyPolygon pp, int idx){ 57 | polyPoint p = { 58 | pp.values[idx*pp.dims+0], 59 | pp.values[idx*pp.dims+1], 60 | }; 61 | return p; 62 | } 63 | 64 | polyMultiPolygon polyMultiPolygonFromGeomSegment(void *segment, int dims){ 65 | polyMultiPolygon mp; 66 | mp.len = (int)((uint32_t*)segment)[0]; 67 | mp.dims = dims; 68 | mp.values = (((uint8_t*)segment)+4); 69 | return mp; 70 | } 71 | 72 | polyPolygon polyMultiPolygonPolygon(polyMultiPolygon mp, int idx){ 73 | uint8_t *segment = mp.values; 74 | for (int i=0;i bbox.max.x) { 106 | bbox.max.x = p.x; 107 | } 108 | if (p.y < bbox.min.y) { 109 | bbox.min.y = p.y; 110 | } else if (p.y > bbox.max.y) { 111 | bbox.max.y = p.y; 112 | } 113 | } 114 | } 115 | return bbox; 116 | } 117 | 118 | // IntersectsRect detects if two bboxes intersect. 119 | int polyRectIntersectsRect(polyRect r, polyRect rect) { 120 | if (r.min.y > rect.max.y || r.max.y < rect.min.y) { 121 | return 0; 122 | } 123 | if (r.min.x > rect.max.x || r.max.x < rect.min.x) { 124 | return 0; 125 | } 126 | return 1; 127 | } 128 | 129 | // InsideRect detects rect is inside of another rect 130 | int polyRectInsideRect(polyRect r, polyRect rect) { 131 | if (r.min.x < rect.min.x || r.max.x > rect.max.x) { 132 | return 0; 133 | } 134 | if (r.min.y < rect.min.y || r.max.y > rect.max.y) { 135 | return 0; 136 | } 137 | return 1; 138 | } 139 | 140 | // String returns a string representation of the polygon. 141 | char *polyPolygonString(polyPolygon pp){ 142 | int len = 0; 143 | char *str = NULL; 144 | str = zmalloc(10); 145 | if (!str){ 146 | return NULL; 147 | } 148 | str[len++] = '{'; 149 | for (int i=0;i 0){ 152 | str[len++] = ','; 153 | str[len++] = ' '; 154 | } 155 | char buf[250]; 156 | char dx[26]; 157 | char dy[26]; 158 | dtoa_grisu3(p.x, dx); 159 | dtoa_grisu3(p.y, dy); 160 | sprintf(buf, "{%s, %s}", dx, dy); 161 | char *nstr = zrealloc(str, len+strlen(buf)+10); 162 | if (!nstr){ 163 | zfree(nstr); 164 | return NULL; 165 | } 166 | str = nstr; 167 | str[len] = '\0'; 168 | strcat(str+len, buf); 169 | len += strlen(buf); 170 | } 171 | str[len++] = '}'; 172 | str[len++] = '\0'; 173 | return str; 174 | } 175 | -------------------------------------------------------------------------------- /src/spatial/poly.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016, Josh Baker 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of Redis nor the names of its contributors may be used 13 | * to endorse or promote products derived from this software without 14 | * specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED by THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #ifndef POLY_H_ 30 | #define POLY_H_ 31 | 32 | #if defined(__cplusplus) 33 | extern "C" { 34 | #endif 35 | 36 | typedef struct polyPoint { 37 | double x, y; 38 | } polyPoint; 39 | 40 | // Rect is rectangle 41 | typedef struct polyRect { 42 | polyPoint min, max; 43 | } polyRect; 44 | 45 | 46 | typedef enum polyRayres{ 47 | RAY_OUT = 0, // outside of the segment. 48 | RAY_LEFT = 1, // to the left of the segment 49 | RAY_ON = 2, // on segment or vertex, special condition 50 | } polyRayres; 51 | 52 | typedef struct polyPolygon { 53 | int len; // number of points. 54 | int dims; // number of dimensions in polygon. 55 | double *values; // point values. this array will be len*dims in size. 56 | } polyPolygon; 57 | 58 | typedef struct polyMultiPolygon { 59 | int len; // number of polygons. 60 | int dims; // number of dimensions in polygon. 61 | void *values; // pointer to the first polygon. 62 | } polyMultiPolygon; 63 | 64 | 65 | // create a polygon from a geom segment. the segment should be composed of a 66 | // 32-bit unsigned int with represents the number of points in the polygon, 67 | // and a series of doubles. The dims value should be 2,3,4 depending on how 68 | // many doubles are required to make up a single point. 69 | polyPolygon polyPolygonFromGeomSegment(void *segment, int dims); 70 | 71 | // same as above, but for multiple polygons 72 | polyMultiPolygon polyMultiPolygonFromGeomSegment(void *segment, int dims); 73 | 74 | const char *polyRayresString(polyRayres r); 75 | polyRayres polyRaycast(polyPoint p, polyPoint a, polyPoint b); 76 | int polyPointInsideRect(polyPoint p, polyRect r); 77 | int polyPolygonInsideRect(polyPolygon pp, polyRect r); 78 | polyPoint polyPolygonPoint(polyPolygon pp, int idx); 79 | polyPolygon polyMultiPolygonPolygon(polyMultiPolygon mp, int idx); 80 | int polyPointInside(polyPoint p, polyPolygon exterior, polyMultiPolygon holes); 81 | int polyPolygonInside(polyPolygon shape, polyPolygon exterior, polyMultiPolygon holes); 82 | polyRect polyPolygonRect(polyPolygon pp); 83 | int polyRectIntersectsRect(polyRect r, polyRect rect); 84 | int polyRectInsideRect(polyRect r, polyRect rect); 85 | char *polyPolygonString(polyPolygon pp); 86 | int polyPointIntersects(polyPoint p, polyPolygon exterior, polyMultiPolygon holes); 87 | int polyPolygonIntersects(polyPolygon shape, polyPolygon exterior, polyMultiPolygon holes); 88 | int polyPolygonContains(polyPolygon shape, polyPolygon exterior, polyMultiPolygon holes); 89 | int polyLinesIntersect(polyPoint a1, polyPoint a2, polyPoint b1, polyPoint b2); 90 | 91 | polyPoint lineCenter(polyPoint a, polyPoint b); 92 | int polyLineIntersect(polyPoint a, polyPoint b, polyPolygon exterior, polyMultiPolygon holes); 93 | int polyLineInside(polyPoint a, polyPoint b, polyPolygon exterior, polyMultiPolygon holes); 94 | int lineintersects(polyPoint a, polyPoint b, polyPoint c, polyPoint d); 95 | 96 | #if defined(__cplusplus) 97 | } 98 | #endif 99 | 100 | #endif /* POLY_H_ */ 101 | -------------------------------------------------------------------------------- /src/spatial/poly_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "poly.h" 7 | #include "test.h" 8 | 9 | polyPoint P(double x, double y); 10 | polyRect RECT(polyPoint min, polyPoint max){ 11 | polyRect r = {min, max}; 12 | return r; 13 | } 14 | 15 | int test_PolyRectIntersects() { 16 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(-1, -1), P(1, 1)))); 17 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(9, 9), P(11, 11)))); 18 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(9, -1), P(11, 1)))); 19 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(-1, 9), P(1, 11)))); 20 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(-1, -1), P(0, 0)))); 21 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(10, 10), P(11, 11)))); 22 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(10, -1), P(11, 0)))); 23 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(-1, 10), P(0, 11)))); 24 | assert(polyRectIntersectsRect(RECT(P(0, 0), P(10, 10)), RECT(P(1, 1), P(2, 2)))); 25 | return 1; 26 | } 27 | 28 | int test_PolyRectInside() { 29 | assert(polyRectInsideRect(RECT(P(1, 1), P(9, 9)), RECT(P(0, 0), P(10, 10)))); 30 | assert(!polyRectInsideRect(RECT(P(-1, -1), P(9, 9)), RECT(P(0, 0), P(10, 10)))); 31 | 32 | return 1; 33 | } -------------------------------------------------------------------------------- /src/spatial/polyinside.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016, Josh Baker 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of Redis nor the names of its contributors may be used 13 | * to endorse or promote products derived from this software without 14 | * specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED by THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #include 30 | #include 31 | #include "poly.h" 32 | 33 | static polyMultiPolygon emptyMultiPolygon = {0,0,0}; 34 | 35 | int insideshpext(polyPoint p, polyPolygon shape, int exterior) { 36 | // if len(shape) < 3 { 37 | // return false 38 | // } 39 | int in = 0; 40 | for (int i = 0; i < shape.len; i++) { 41 | polyPoint a = polyPolygonPoint(shape, i); 42 | polyPoint b = polyPolygonPoint(shape, (i+1)%shape.len); 43 | polyRayres res = polyRaycast(p, a, b); 44 | if (res == RAY_ON) { 45 | return exterior; 46 | } 47 | 48 | if (res == RAY_LEFT) { 49 | in = !in; 50 | } 51 | } 52 | return in; 53 | } 54 | 55 | 56 | // Inside returns true if point is inside of exterior and not in a hole. 57 | // The validity of the exterior and holes must be done elsewhere and are assumed valid. 58 | // A valid exterior is a near-linear ring. 59 | // A valid hole is one that is full contained inside the exterior. 60 | // A valid hole may not share the same segment line as the exterior. 61 | int polyPointInside(polyPoint p, polyPolygon exterior, polyMultiPolygon holes) { 62 | if (!insideshpext(p, exterior, 1)) { 63 | return 0; 64 | } 65 | for (int i=0;i 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "zmalloc.h" 7 | #include "poly.h" 8 | #include "test.h" 9 | 10 | static polyMultiPolygon emptyMultiPolygon = {0,0,0}; 11 | 12 | int insideshpext(polyPoint p, polyPolygon shape, int exterior); 13 | 14 | polyPoint P(double x, double y){ 15 | polyPoint p = {x, y}; 16 | return p; 17 | } 18 | 19 | 20 | 21 | void *makeSegment(int count, ...){ 22 | void *segment = zmalloc(4+count*16); 23 | assert(segment); 24 | ((uint32_t*)segment)[0] = count; 25 | va_list ap; 26 | va_start(ap, count); 27 | for (int i=0;i 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of Redis nor the names of its contributors may be used 13 | * to endorse or promote products derived from this software without 14 | * specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED by THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #include 30 | #include "poly.h" 31 | 32 | static polyMultiPolygon emptyMultiPolygon = {0,0,0}; 33 | 34 | polyPoint lineCenter(polyPoint a, polyPoint b){ 35 | polyPoint p={ 36 | (a.x+b.x)/2, (a.y+b.y)/2 37 | }; 38 | 39 | return p; 40 | } 41 | 42 | int lineintersects( 43 | polyPoint a, polyPoint b, // segment 1 44 | polyPoint c, polyPoint d // segment 2 45 | ){ 46 | // do the bounding boxes intersect? 47 | // the following checks without swapping values. 48 | if (a.y > b.y) { 49 | if (c.y > d.y) { 50 | if (b.y > c.y || a.y < d.y) { 51 | return 0; 52 | } 53 | } else { 54 | if (b.y > d.y || a.y < c.y) { 55 | return 0; 56 | } 57 | } 58 | } else { 59 | if (c.y > d.y) { 60 | if (a.y > c.y || b.y < d.y) { 61 | return 0; 62 | } 63 | } else { 64 | if (a.y > d.y || b.y < c.y) { 65 | return 0; 66 | } 67 | } 68 | } 69 | if (a.x > b.x) { 70 | if (c.x > d.x) { 71 | if (b.x > c.x || a.x < d.x) { 72 | return 0; 73 | } 74 | } else { 75 | if (b.x > d.x || a.x < c.x) { 76 | return 0; 77 | } 78 | } 79 | } else { 80 | if (c.x > d.x) { 81 | if (a.x > c.x || b.x < d.x) { 82 | return 0; 83 | } 84 | } else { 85 | if (a.x > d.x || b.x < c.x) { 86 | return 0; 87 | } 88 | } 89 | } 90 | 91 | // the following code is from http://ideone.com/PnPJgb 92 | double cmpx = c.x-a.x; 93 | double cmpy = c.y-a.y; 94 | double rx = b.x-a.x; 95 | double ry = b.y-a.y; 96 | double cmpxr = cmpx*ry - cmpy*rx; 97 | if (cmpxr == 0) { 98 | // Lines are collinear, and so intersect if they have any overlap 99 | if (!(((c.x-a.x <= 0) != (c.x-b.x <= 0)) || ((c.y-a.y <= 0) != (c.y-b.y <= 0)))) { 100 | return 0; 101 | } 102 | return 1; 103 | } 104 | double sx = d.x-c.x; 105 | double sy = d.y-c.y; 106 | double cmpxs = cmpx*sy - cmpy*sx; 107 | double rxs = rx*sy - ry*sx; 108 | if (rxs == 0) { 109 | return 0; // Lines are parallel. 110 | } 111 | double rxsr = 1 / rxs; 112 | double t = cmpxs * rxsr; 113 | double u = cmpxr * rxsr; 114 | if (!((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1))) { 115 | return 0; 116 | } 117 | return 1; 118 | } 119 | 120 | // Intersects detects if a point intersects another polygon 121 | int polyPointIntersects(polyPoint p, polyPolygon exterior, polyMultiPolygon holes){ 122 | return polyPointInside(p, exterior, holes); 123 | } 124 | 125 | int polyLinesIntersect(polyPoint a1, polyPoint a2, polyPoint b1, polyPoint b2){ 126 | return lineintersects(a1,a2,b1,b2); 127 | } 128 | 129 | // Intersects detects if a polygon intersects another polygon 130 | int polyPolygonIntersects(polyPolygon shape, polyPolygon exterior, polyMultiPolygon holes) { 131 | switch (shape.len) { 132 | case 0: 133 | return 0; 134 | case 1: 135 | switch (exterior.len) { 136 | case 0: 137 | return 0; 138 | case 1:{ 139 | polyPoint shape0 = polyPolygonPoint(shape, 0); 140 | polyPoint exterior0 = polyPolygonPoint(exterior, 0); 141 | return shape0.x == exterior0.x && shape0.y == shape0.y; 142 | } 143 | default: 144 | return polyPointInside(polyPolygonPoint(shape, 0), exterior, holes); 145 | } 146 | default: 147 | switch (exterior.len) { 148 | case 0: 149 | return 0; 150 | case 1: 151 | return polyPointInside(polyPolygonPoint(exterior, 0), shape, holes); 152 | } 153 | } 154 | if (!polyRectIntersectsRect(polyPolygonRect(shape), polyPolygonRect(exterior))) { 155 | return 0; 156 | } 157 | for (int i=0;i 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "poly.h" 7 | #include "test.h" 8 | 9 | static polyMultiPolygon emptyMultiPolygon = {0,0,0}; 10 | 11 | polyPoint P(double x, double y); 12 | void *makeSegment(int count, ...); 13 | void *makeMultiSegment(int count, ...); 14 | 15 | #define POLYGON(count, ...) (polyPolygonFromGeomSegment(makeSegment(count, __VA_ARGS__), 2)) 16 | #define MULTIPOLYGON(count, ...) (polyMultiPolygonFromGeomSegment(makeMultiSegment(count, __VA_ARGS__), 2)) 17 | 18 | int lineintersects( 19 | polyPoint a, polyPoint b, // segment 1 20 | polyPoint c, polyPoint d // segment 2 21 | ); 22 | 23 | void testIntersectsLinesA(polyPoint a, polyPoint b, polyPoint c, polyPoint d, int expect) { 24 | int res = lineintersects(a, b, c, d); 25 | assert(res == expect); 26 | res = lineintersects(b, a, c, d); 27 | assert(res == expect); 28 | res = lineintersects(a, b, d, c); 29 | assert(res == expect); 30 | res = lineintersects(b, a, d, c); 31 | assert(res == expect); 32 | } 33 | 34 | void testIntersectsLines(polyPoint a, polyPoint b, polyPoint c, polyPoint d, int expect) { 35 | testIntersectsLinesA(a, b, c, d, expect); 36 | testIntersectsLinesA(c, d, a, b, expect); 37 | } 38 | 39 | int test_PolyIntersectsLines() { 40 | testIntersectsLines(P(0, 6), P(12, -6), P(0, 0), P(12, 0), 1); 41 | testIntersectsLines(P(0, 0), P(5, 5), P(5, 5), P(0, 10), 1); 42 | testIntersectsLines(P(0, 0), P(5, 5), P(5, 6), P(0, 10), 0); 43 | testIntersectsLines(P(0, 0), P(5, 5), P(5, 4), P(0, 10), 1); 44 | testIntersectsLines(P(0, 0), P(2, 2), P(0, 2), P(2, 0), 1); 45 | testIntersectsLines(P(0, 0), P(2, 2), P(0, 2), P(1, 1), 1); 46 | testIntersectsLines(P(0, 0), P(2, 2), P(2, 0), P(1, 1), 1); 47 | testIntersectsLines(P(0, 0), P(0, 4), P(1, 4), P(4, 1), 0); 48 | testIntersectsLines(P(0, 0), P(0, 4), P(1, 4), P(4, 4), 0); 49 | testIntersectsLines(P(0, 0), P(0, 4), P(4, 1), P(4, 4), 0); 50 | testIntersectsLines(P(0, 0), P(4, 0), P(1, 4), P(4, 1), 0); 51 | testIntersectsLines(P(0, 0), P(4, 0), P(1, 4), P(4, 4), 0); 52 | testIntersectsLines(P(0, 0), P(4, 0), P(4, 1), P(4, 4), 0); 53 | testIntersectsLines(P(0, 4), P(4, 0), P(1, 4), P(4, 1), 0); 54 | testIntersectsLines(P(0, 4), P(4, 0), P(1, 4), P(4, 4), 0); 55 | testIntersectsLines(P(0, 4), P(4, 0), P(4, 1), P(4, 4), 0); 56 | return 1; 57 | } 58 | 59 | void testIntersectsShapes(polyPolygon exterior, polyMultiPolygon holes, polyPolygon shape, int expect) { 60 | int got = polyPolygonIntersects(shape, exterior, holes); 61 | assert(got == expect); 62 | got = polyPolygonIntersects(exterior, shape, emptyMultiPolygon); 63 | assert(got == expect); 64 | } 65 | 66 | 67 | int test_PolyIntersectsShapes() { 68 | testIntersectsShapes( 69 | POLYGON(4, P(6, 0), P(12, 0), P(12, -6), P(6, 0)), 70 | emptyMultiPolygon, 71 | POLYGON(4, P(0, 0), P(0, 6), P(6, 0), P(0, 0)), 72 | 1); 73 | 74 | testIntersectsShapes( 75 | POLYGON(4, P(7, 0), P(12, 0), P(12, -6), P(7, 0)), 76 | emptyMultiPolygon, 77 | POLYGON(4, P(0, 0), P(0, 6), P(6, 0), P(0, 0)), 78 | 0); 79 | 80 | testIntersectsShapes( 81 | POLYGON(4, P(0.5, 0.5), P(0.5, 4.5), P(4.5, 0.5), P(0.5, 0.5)), 82 | emptyMultiPolygon, 83 | POLYGON(4, P(0, 0), P(0, 6), P(6, 0), P(0, 0)), 84 | 1); 85 | 86 | testIntersectsShapes( 87 | POLYGON(4, P(0, 0), P(0, 6), P(6, 0), P(0, 0)), 88 | MULTIPOLYGON(1, POLYGON(5, P(1, 1), P(1, 2), P(2, 2), P(2, 1), P(1, 1))), 89 | POLYGON(4, P(0.5, 0.5), P(0.5, 4.5), P(4.5, 0.5), P(0.5, 0.5)), 90 | 1); 91 | 92 | testIntersectsShapes( 93 | POLYGON(5, P(0, 0), P(0, 10), P(10, 10), P(10, 0), P(0, 0)), 94 | MULTIPOLYGON(1, POLYGON(5, P(2, 2), P(2, 6), P(6, 6), P(6, 2), P(2, 2))), 95 | POLYGON(5, P(1, 1), P(1, 9), P(9, 9), P(9, 1), P(1, 1)), 96 | 1); 97 | return 1; 98 | } 99 | -------------------------------------------------------------------------------- /src/spatial/polyraycast.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016, Josh Baker 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of Redis nor the names of its contributors may be used 13 | * to endorse or promote products derived from this software without 14 | * specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED by THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | // This implementation of the raycast algorithm test if (a point is 30 | // to the left of a line, or on the segment line. Otherwise it is 31 | // assumed that the point is outside of the segment line. 32 | 33 | #include "poly.h" 34 | 35 | const char *polyRayresString(polyRayres r){ 36 | switch (r) { 37 | default: 38 | return "unknown"; 39 | case RAY_OUT: 40 | return "out"; 41 | case RAY_LEFT: 42 | return "left"; 43 | case RAY_ON: 44 | return "on"; 45 | } 46 | } 47 | 48 | polyRayres polyRaycast(polyPoint p, polyPoint a, polyPoint b) { 49 | // Algorithm 1, There may be a potential bug in it. 50 | /* if (a.y == b.y) { 51 | // A and B share the same Y plane. 52 | if (a.x == b.x) { 53 | // AB is just a point. 54 | if (p.x == a.x && p.y == a.y) { 55 | return RAY_ON; 56 | } 57 | return RAY_OUT; 58 | } 59 | // AB is a horizontal line. 60 | if (p.y != a.y) { 61 | // P is not on same Y plane as A and B. 62 | return RAY_OUT; 63 | } 64 | // P is on same Y plane as A and B 65 | if (a.x < b.x) { 66 | if (p.x >= a.x && p.x <= b.x) { 67 | return RAY_ON; 68 | } 69 | if (p.x < a.x) { 70 | return RAY_LEFT; 71 | } 72 | } else { 73 | if (p.x >= b.x && p.x <= a.x) { 74 | return RAY_ON; 75 | } 76 | if (p.x < b.x) { 77 | return RAY_LEFT; 78 | } 79 | } 80 | return RAY_OUT; 81 | } 82 | if (a.x == b.x) { 83 | // AB is a vertical line. 84 | if (a.y > b.y) { 85 | // A is above B 86 | if (p.y > a.y || p.y < b.y) { 87 | return RAY_OUT; 88 | } 89 | } else { 90 | // B is above A 91 | if (p.y > b.y || p.y < a.y) { 92 | return RAY_OUT; 93 | } 94 | } 95 | if (p.x == a.x) { 96 | return RAY_ON; 97 | } 98 | if (p.x < a.x) { 99 | return RAY_LEFT; 100 | } 101 | return RAY_OUT; 102 | } 103 | 104 | // AB is an angled line 105 | if (a.y > b.y) { 106 | // swap A and B so that A is below B. 107 | double tx = a.x; 108 | double ty = a.y; 109 | a.x = b.x; 110 | a.y = b.y; 111 | b.x = tx; 112 | b.y = ty; 113 | } 114 | if (p.y < a.y || p.y > b.y) { 115 | return RAY_OUT; 116 | } 117 | if (a.x < b.x) { 118 | if (p.x < a.x) { 119 | return RAY_LEFT; 120 | } 121 | if (p.x > b.x) { 122 | return RAY_OUT; 123 | } 124 | } else { 125 | if (p.x < b.x) { 126 | return RAY_LEFT; 127 | } 128 | if (p.x > a.x) { 129 | return RAY_OUT; 130 | } 131 | } 132 | if ((p.x == a.x && p.y == a.y) || (p.x == b.x && p.y == b.y)) { 133 | // P is on a vertex. 134 | return RAY_ON; 135 | } 136 | double v1 = (p.y - a.y) / (p.x - a.x); 137 | double v2 = (b.y - a.y) / (b.x - a.x); 138 | if (p.x != a.x) { 139 | if (v1-v2 == 0) { 140 | // P is on a segment 141 | return RAY_ON; 142 | } 143 | if (v1 >= v2) { 144 | return RAY_LEFT; 145 | } 146 | return RAY_OUT; 147 | } else { 148 | if (v2 < 0 || (v2 > 0 && p.y < a.y)) { 149 | return RAY_OUT; 150 | } else { 151 | return RAY_LEFT; 152 | } 153 | } 154 | */ 155 | 156 | // Algorithm 2 by W. Randolph Franklin 157 | if (a.x == b.x && a.y == b.y) { 158 | if (p.x == a.x && p.y == a.y) { 159 | return RAY_ON; 160 | } 161 | return RAY_OUT; 162 | } 163 | // try to check if p is on line ab and p is on segment ab. 164 | if (((a.x <= p.x && p.x <= b.x) || (b.x <= p.x && p.x <= a.x)) && ((a.y <= p.y && p.y <= b.y) || (b.y <= p.y && p.y <= a.y)) && (p.y - a.y) * (b.x - a.x) == (p.x - a.x) * (b.y - a.y)) { 165 | return RAY_ON; 166 | } else { 167 | // below tries to check: 168 | // min(a.y, b.y) <= p.y < max(a.y, b.y); when ab is parallel with y=p.y, it is always false; 169 | // assume Q is the crosspoint of line y=p.y and line ab, check if p.x < Q.x; 170 | if (((a.y > p.y) != (b.y > p.y)) && (p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x)) { 171 | return RAY_LEFT; 172 | } else { 173 | return RAY_OUT; 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/spatial/rtree.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016, Josh Baker . 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * * Neither the name of Redis nor the names of its contributors may be used 14 | * to endorse or promote products derived from this software without 15 | * specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 | * THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #define NUM_DIMS 2 31 | 32 | #include "zmalloc.h" 33 | #include "rtree_tmpl.c" 34 | #include "rtree.h" 35 | 36 | typedef struct rtreeIterator { 37 | iteratorT *iterator; 38 | } rtreeIterator; 39 | 40 | rtree *rtreeNew() { 41 | rtree *tr = zmalloc(sizeof(rtree)); 42 | if (!tr){ 43 | return NULL; 44 | } 45 | memset(tr, 0, sizeof(rtree)); 46 | return tr; 47 | } 48 | 49 | void rtreeFree(rtree *tr){ 50 | if (!tr){ 51 | return; 52 | } 53 | if (tr->root){ 54 | freeNode(tr->root); 55 | } 56 | zfree(tr); 57 | } 58 | 59 | /* Remove removes item from rtree */ 60 | int rtreeRemove(rtree *tr, double minX, double minY, double maxX, double maxY, void *item) { 61 | if (tr && tr->root) { 62 | return removeRect(makeRect(minX, minY, maxX, maxY), item, &(tr->root)) ? 0 : 1; 63 | } 64 | return 0; 65 | } 66 | 67 | // Count return the number of items in rtree. 68 | int rtreeCount(rtree *tr) { 69 | if (!tr || !tr->root){ 70 | return 0; 71 | } 72 | return countRec(tr->root, 0); 73 | } 74 | 75 | // Insert inserts item into rtree 76 | int rtreeInsert(rtree *tr, double minX, double minY, double maxX, double maxY, void *item) { 77 | if (!tr){ 78 | return 0; 79 | } 80 | if (!tr->root) { 81 | tr->root = zmalloc(sizeof(nodeT)); 82 | if (!tr->root){ 83 | return 0; 84 | } 85 | memset(tr->root, 0, sizeof(nodeT)); 86 | } 87 | insertRect(makeRect(minX, minY, maxX, maxY), item, NULL, &(tr->root), 0); 88 | return 1; 89 | } 90 | 91 | void rtreeRemoveAll(rtree *tr){ 92 | if (tr && tr->root){ 93 | freeNode(tr->root); 94 | tr->root = NULL; 95 | } 96 | } 97 | 98 | typedef struct iteratorUserData { 99 | rtreeSearchFunc iterator; 100 | void *userdata; 101 | } iteratorUserData; 102 | 103 | static int iteratorFunc(rectT rect, void *item, void *userdata){ 104 | iteratorUserData *ud = userdata; 105 | double minX, minY, maxX, maxY; 106 | getRect(rect, &minX, &minY, &maxX, &maxY); 107 | return ud->iterator(minX, minY, maxX, maxY, item, ud->userdata); 108 | } 109 | 110 | int rtreeSearch(rtree *tr, double minX, double minY, double maxX, double maxY, rtreeSearchFunc iterator, void *userdata){ 111 | if (!tr || !tr->root){ 112 | return 0; 113 | } 114 | if (iterator) { 115 | iteratorUserData ud = {iterator, userdata}; 116 | return search(tr->root, makeRect(minX, minY, maxX, maxY), iteratorFunc, &ud); 117 | } else{ 118 | return search(tr->root, makeRect(minX, minY, maxX, maxY), NULL, NULL); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/spatial/rtree.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016, Josh Baker . 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * * Neither the name of Redis nor the names of its contributors may be used 14 | * to endorse or promote products derived from this software without 15 | * specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 | * THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef RTREE_H_ 31 | #define RTREE_H_ 32 | 33 | #if defined(__cplusplus) 34 | extern "C" { 35 | #endif 36 | 37 | #include "geom.h" 38 | 39 | typedef struct rtree { 40 | void *root; 41 | } rtree; 42 | 43 | rtree *rtreeNew(); 44 | void rtreeFree(rtree *tr); 45 | int rtreeRemove(rtree *tr, double minX, double minY, double maxX, double maxY, void *item); 46 | void rtreeRemoveAll(rtree *tr); 47 | int rtreeCount(rtree *tr); 48 | int rtreeInsert(rtree *tr, double minX, double minY, double maxX, double maxY, void *item); 49 | typedef int(*rtreeSearchFunc)(double minX, double minY, double maxX, double maxY, void *item, void *userdata); 50 | int rtreeSearch(rtree *tr, double minX, double minY, double maxX, double maxY, rtreeSearchFunc iterator, void *userdata); 51 | 52 | #if defined(__cplusplus) 53 | } 54 | #endif 55 | #endif /* RTREE_H_ */ 56 | -------------------------------------------------------------------------------- /src/spatial/rtree_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "test.h" 9 | #include "rtree.h" 10 | 11 | static double randd() { return ((rand()%RAND_MAX) / (double)RAND_MAX); } 12 | static double randx() { return randd() * 360.0 - 180.0; } 13 | static double randy() { return randd() * 180.0 - 90.0; } 14 | 15 | int iterator(double minX, double minY, double maxX, double maxY, void *item, void *userdata){ 16 | return 1; 17 | } 18 | 19 | static rtree *insert(){ 20 | srand(time(NULL)/clock()); 21 | rtree *tr = rtreeNew(); 22 | assert(tr); 23 | 24 | int n = 100000; 25 | //clock_t start = clock(); 26 | for (int i=0;i= 40 && rtC/n <= 80); 59 | //printf("searched %d queries in %.2f secs, %.0f ops/s (%.0f items)\n", n, elapsed, (double)n/elapsed, rtC/(double)n); 60 | rtreeFree(tr); 61 | return 1; 62 | } 63 | 64 | 65 | int test_RTreeRemove(){ 66 | rtree *tr = rtreeNew(); 67 | assert(tr); 68 | 69 | assert(rtreeInsert(tr, 10, 10, 20, 20, (void*)(long)100)); 70 | assert(rtreeInsert(tr, 30, 30, 50, 50, (void*)(long)101)); 71 | assert(rtreeCount(tr)==2); 72 | 73 | assert(rtreeRemove(tr, 30, 30, 50, 50, (void*)(long)101)); 74 | assert(rtreeCount(tr)==1); 75 | assert(!rtreeRemove(tr, 30, 30, 50, 50, (void*)(long)101)); 76 | 77 | assert(rtreeRemove(tr, 0, 0, 30, 30, (void*)(long)100)); 78 | assert(rtreeCount(tr)==0); 79 | assert(!rtreeRemove(tr, 0, 0, 30, 30, (void*)(long)100)); 80 | 81 | rtreeRemoveAll(tr); 82 | assert(rtreeCount(tr)==0); 83 | 84 | assert(rtreeInsert(tr, 10, 10, 20, 20, (void*)(long)100)); 85 | assert(rtreeInsert(tr, 30, 30, 50, 50, (void*)(long)101)); 86 | assert(rtreeCount(tr)==2); 87 | 88 | rtreeRemoveAll(tr); 89 | assert(rtreeCount(tr)==0); 90 | assert(!rtreeRemove(tr, 10, 10, 20, 20, (void*)(long)100)); 91 | assert(!rtreeRemove(tr, 30, 30, 50, 50, (void*)(long)101)); 92 | 93 | 94 | 95 | rtreeFree(tr); 96 | return 1; 97 | } -------------------------------------------------------------------------------- /src/spatial/test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016, Josh Baker . 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * * Neither the name of Redis nor the names of its contributors may be used 14 | * to endorse or promote products derived from this software without 15 | * specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 | * THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include "test.h" 36 | 37 | int test_Geom(); 38 | int test_GeomZ(); 39 | int test_GeomZM(); 40 | int test_GeomPoint(); 41 | int test_GeomMultiPoint(); 42 | int test_GeomLineString(); 43 | int test_GeomMultiLineString(); 44 | int test_GeomPolygon(); 45 | int test_GeomMultiPolygon(); 46 | int test_GeomGeometryCollection(); 47 | int test_GeomIterator(); 48 | int test_GeomPolyMap(); 49 | int test_RTreeInsert(); 50 | int test_RTreeSearch(); 51 | int test_RTreeRemove(); 52 | int test_GeoUtilDistance(); 53 | int test_GeoUtilDestination(); 54 | int test_PolyRayInside(); 55 | int test_PolyRayExteriorHoles(); 56 | int test_PolyInsideShapes(); 57 | int test_PolyIntersectsLines(); 58 | int test_PolyIntersectsShapes(); 59 | int test_PolyRectIntersects(); 60 | int test_PolyRectInside(); 61 | 62 | 63 | int test_GeomPolyMapPointBench(); 64 | int test_GeomPolyMapPolygonBench(); 65 | int test_GeomPolyMapGeometryCollectionBench(); 66 | 67 | int test_GeomPolyMapPointBenchSingleThreaded(); 68 | int test_GeomPolyMapPolygonBenchSingleThreaded(); 69 | int test_GeomPolyMapGeometryCollectionBenchSingleThreaded(); 70 | 71 | int test_GeomPolyMapIntersects(); 72 | int test_GeomPolyMapWithin(); 73 | 74 | 75 | 76 | 77 | typedef struct test{ 78 | char *name; 79 | int (*test)(); 80 | } test; 81 | 82 | test tests[] = { 83 | { "geom", test_Geom }, 84 | { "geomZ", test_GeomZ }, 85 | { "geomZM", test_GeomZM }, 86 | { "geomPoint", test_GeomPoint }, 87 | { "geomMultiPoint", test_GeomMultiPoint }, 88 | { "geomLineString", test_GeomLineString }, 89 | { "geomMultiLineString", test_GeomMultiLineString }, 90 | { "geomPolygon", test_GeomPolygon }, 91 | { "geomMultiPolygon", test_GeomMultiPolygon }, 92 | { "geomGeometryCollection", test_GeomGeometryCollection }, 93 | { "geomIterator", test_GeomIterator }, 94 | { "geomPolyMap", test_GeomPolyMap }, 95 | 96 | { "rtreeInsert", test_RTreeInsert }, 97 | { "rtreeSearch", test_RTreeSearch }, 98 | { "rtreeRemove", test_RTreeRemove }, 99 | 100 | { "geoutilDistance", test_GeoUtilDistance }, 101 | { "geoutilDestination", test_GeoUtilDestination }, 102 | 103 | { "polyRayInside", test_PolyRayInside }, 104 | { "polyRayExteriorHoles", test_PolyRayExteriorHoles }, 105 | { "polyInsideShapes", test_PolyInsideShapes }, 106 | { "polyIntersectsLines", test_PolyIntersectsLines }, 107 | { "polyIntersectsShapes", test_PolyIntersectsShapes }, 108 | { "polyRectIntersects", test_PolyRectIntersects }, 109 | { "polyRectInside", test_PolyRectInside }, 110 | 111 | { "polyMapPointBench", test_GeomPolyMapPointBench }, 112 | { "polyMapPolygonBench", test_GeomPolyMapPolygonBench }, 113 | { "polyMapGeomColBench", test_GeomPolyMapGeometryCollectionBench }, 114 | { "polyMapPointBenchSingleThreaded", test_GeomPolyMapPointBenchSingleThreaded }, 115 | { "polyMapPolygonBenchSingleThreaded", test_GeomPolyMapPolygonBenchSingleThreaded }, 116 | { "polyMapGeomColBenchSingleThreaded", test_GeomPolyMapGeometryCollectionBenchSingleThreaded }, 117 | 118 | { "searchPolyMapIntersects", test_GeomPolyMapIntersects }, 119 | { "searchPolyMapWithin", test_GeomPolyMapWithin }, 120 | 121 | }; 122 | 123 | static int abort_handled = 0; 124 | void __tassert_fail(const char *what, const char *file, int line, const char *func){ 125 | printf("\x1b[31m[failed]\x1b[0m\n"); 126 | printf(" fail: assert(%s)\n func: %s\n file: %s\n line: %d\n", what, func, file, line); 127 | abort_handled = 1; 128 | abort(); 129 | } 130 | 131 | void sig_handler(int sig) { 132 | if (!abort_handled){ 133 | printf("\x1b[31m[failed]\x1b[0m\n"); 134 | if (sig == SIGABRT){ 135 | printf(" \x1b[33muse \"test.h\" for more details.\x1b[0m\n"); 136 | } 137 | abort_handled = 1; 138 | }else { 139 | printf("\x1b[0m\n"); 140 | } 141 | if (sig == SIGSEGV){ 142 | printf("Segmentation fault: 11\n"); 143 | } 144 | exit(1); 145 | } 146 | 147 | static clock_t start_c = 0; 148 | static clock_t stop_c = 0; 149 | 150 | void stopClock(){ 151 | stop_c = clock(); 152 | } 153 | 154 | void restartClock(){ 155 | start_c = clock(); 156 | stop_c = 0; 157 | } 158 | 159 | int main(int argc, const char **argv) { 160 | signal(SIGSEGV, sig_handler); 161 | signal(SIGTERM, sig_handler); 162 | signal(SIGHUP, sig_handler); 163 | signal(SIGINT, sig_handler); 164 | signal(SIGABRT, sig_handler); 165 | 166 | const char *run = getenv("RUNTEST"); 167 | if (run == NULL){ 168 | run = ""; 169 | } 170 | 171 | for (int i=1;i mlabelsz){ 196 | mlabelsz = strlen(label); 197 | } 198 | } 199 | 200 | for (int i=0;i 1){ 223 | double opss = ((double)res)/elapsed; 224 | printf(", op/s %.0f", opss); 225 | } 226 | printf("\n"); 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/spatial/test.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_H_ 2 | #define TEST_H_ 3 | 4 | #if defined(__cplusplus) 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | 10 | #define tassert(x) ((void)((x) || (__tassert_fail(#x, __FILE__, __LINE__, __func__),0))) 11 | #ifdef assert 12 | #undef assert 13 | #define assert tassert 14 | #endif 15 | 16 | void __tassert_fail(const char *what, const char *file, int line, const char *func); 17 | 18 | void stopClock(); 19 | void restartClock(); 20 | 21 | #if defined(__cplusplus) 22 | } 23 | #endif 24 | #endif /* TEST_H_ */ -------------------------------------------------------------------------------- /src/spatial/zmalloc.h: -------------------------------------------------------------------------------- 1 | /* For now just use stdlib malloc, but may be expaned in the future. */ 2 | 3 | #ifndef ZMALLOC_H_ 4 | #define ZMALLOC_H_ 5 | 6 | #if defined(__cplusplus) 7 | extern "C" { 8 | #endif 9 | 10 | #include "../redismodule.h" 11 | 12 | #define zmalloc(size) (RedisModule_Alloc((size))) 13 | #define zrealloc(ptr,size) (RedisModule_Realloc((ptr),(size))) 14 | #define zfree(ptr) (RedisModule_Free((ptr))) 15 | 16 | #if defined(__cplusplus) 17 | } 18 | #endif 19 | #endif /* ZMALLOC_H_ */ -------------------------------------------------------------------------------- /src/tairgis.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef TAIRGIS_H 18 | #define TAIRGIS_H 19 | 20 | #define GIS_WITHVALUE (1<<0) 21 | #define GIS_WITHDIST (1<<1) 22 | #define GIS_SORT_ASC (1<<2) 23 | #define GIS_SORT_DESC (1<<3) 24 | 25 | #endif // TAIRGIS_H 26 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "util.h" 18 | #include "redismodule.h" 19 | 20 | #include 21 | 22 | /* Return the number of digits of 'v' when converted to string in radix 10. 23 | * See ll2string() for more information. */ 24 | uint32_t GisModule_digits10(uint64_t v) { 25 | if (v < 10) return 1; 26 | if (v < 100) return 2; 27 | if (v < 1000) return 3; 28 | if (v < 1000000000000UL) { 29 | if (v < 100000000UL) { 30 | if (v < 1000000) { 31 | if (v < 10000) return 4; 32 | return (uint32_t) 5 + (v >= 100000); 33 | } 34 | return (uint32_t) 7 + (v >= 10000000UL); 35 | } 36 | if (v < 10000000000UL) { 37 | return (uint32_t) 9 + (v >= 1000000000UL); 38 | } 39 | return (uint32_t) 11 + (v >= 100000000000UL); 40 | } 41 | return 12 + GisModule_digits10(v / 1000000000000UL); 42 | } 43 | 44 | /* Convert a long long into a string. Returns the number of 45 | * characters needed to represent the number. 46 | * If the buffer is not big enough to store the string, 0 is returned. 47 | * 48 | * Based on the following article (that apparently does not provide a 49 | * novel approach but only publicizes an already used technique): 50 | * 51 | * https://www.facebook.com/notes/facebook-engineering/three-optimization-tips-for-c/10151361643253920 52 | * 53 | * Modified in order to handle signed integers since the original code was 54 | * designed for unsigned integers. */ 55 | int GisModule_ll2string(char *dst, size_t dstlen, long long svalue) { 56 | static const char digits[201] = 57 | "0001020304050607080910111213141516171819" 58 | "2021222324252627282930313233343536373839" 59 | "4041424344454647484950515253545556575859" 60 | "6061626364656667686970717273747576777879" 61 | "8081828384858687888990919293949596979899"; 62 | int negative; 63 | unsigned long long value; 64 | 65 | /* The main loop works with 64bit unsigned integers for simplicity, so 66 | * we convert the number here and remember if it is negative. */ 67 | if (svalue < 0) { 68 | if (svalue != LLONG_MIN) { 69 | value = -svalue; 70 | } else { 71 | value = ((unsigned long long) LLONG_MAX)+1; 72 | } 73 | negative = 1; 74 | } else { 75 | value = svalue; 76 | negative = 0; 77 | } 78 | 79 | /* Check length. */ 80 | uint32_t const length = GisModule_digits10(value)+negative; 81 | if (length >= dstlen) return 0; 82 | 83 | /* Null term. */ 84 | uint32_t next = length; 85 | dst[next] = '\0'; 86 | next--; 87 | while (value >= 100) { 88 | int const i = (value % 100) * 2; 89 | value /= 100; 90 | dst[next] = digits[i + 1]; 91 | dst[next - 1] = digits[i]; 92 | next -= 2; 93 | } 94 | 95 | /* Handle last 1-2 digits. */ 96 | if (value < 10) { 97 | dst[next] = '0' + (uint32_t) value; 98 | } else { 99 | int i = (uint32_t) value * 2; 100 | dst[next] = digits[i + 1]; 101 | dst[next - 1] = digits[i]; 102 | } 103 | 104 | /* Add sign. */ 105 | if (negative) dst[0] = '-'; 106 | return length; 107 | } 108 | 109 | int GisModule_DictInsertOrUpdate(RedisModuleDict *d, RedisModuleString *field, RedisModuleString *value) { 110 | int nokey = 0; 111 | RedisModuleString *oldvalue, *newvalue = RedisModule_CreateStringFromString(NULL, value); 112 | nokey = RedisModule_DictDel(d, field, &oldvalue); 113 | if (nokey == REDISMODULE_OK) { 114 | GisModule_FreeStringSafe(NULL, oldvalue); 115 | } 116 | return RedisModule_DictSet(d, field, newvalue); 117 | } 118 | 119 | void GisModule_FreeStringSafe(RedisModuleCtx *ctx, RedisModuleString *str) { 120 | if (!str) return; 121 | RedisModule_FreeString(ctx, str); 122 | } 123 | 124 | int GisModule_GetDoubleFromObjectOrReply(RedisModuleCtx *ctx, RedisModuleString *s, double *target, const char *msg) { 125 | double value; 126 | if (RedisModule_StringToDouble(s, &value) != REDISMODULE_OK) { 127 | if (msg != NULL) { 128 | RedisModule_ReplyWithError(ctx, (char *) msg); 129 | } else { 130 | RedisModule_ReplyWithError(ctx, "ERR value is not a valid float"); 131 | } 132 | return REDISMODULE_ERR; 133 | } 134 | *target = value; 135 | return REDISMODULE_OK; 136 | } 137 | 138 | int GisModule_ExtractUnitOrReply(RedisModuleCtx *ctx, RedisModuleString *sunit, double *to_meters) { 139 | const char *unit = RedisModule_StringPtrLen(sunit, NULL); 140 | if (!strcasecmp(unit, "m")) { 141 | *to_meters = 1; 142 | } else if (!strcasecmp(unit, "km")) { 143 | *to_meters = 1000; 144 | } else if (!strcasecmp(unit, "ft")) { 145 | *to_meters = 0.3048; 146 | } else if (!strcasecmp(unit, "mi")) { 147 | *to_meters = 1609.34; 148 | } else { 149 | RedisModule_ReplyWithError(ctx, 150 | "ERR unsupported unit provided. please use m, km, ft, mi"); 151 | return REDISMODULE_ERR; 152 | } 153 | return REDISMODULE_OK; 154 | } 155 | 156 | void GisModule_AddReplyDistance(RedisModuleCtx *ctx, double d) { 157 | char dbuf[128]; 158 | size_t dlen = snprintf(dbuf, sizeof(dbuf), "%.4f", d); 159 | RedisModule_ReplyWithStringBuffer(ctx, dbuf, dlen); 160 | } 161 | 162 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef UTIL_H 18 | #define UTIL_H 19 | 20 | #include 21 | #include 22 | #include 23 | #include "redismodule.h" 24 | 25 | uint32_t GisModule_digits10(uint64_t v); 26 | int GisModule_ll2string(char *dst, size_t dstlen, long long svalue); 27 | int GisModule_DictInsertOrUpdate(RedisModuleDict *d, RedisModuleString *field, RedisModuleString *value); 28 | void GisModule_FreeStringSafe(RedisModuleCtx *ctx, RedisModuleString *str); 29 | int GisModule_GetDoubleFromObjectOrReply(RedisModuleCtx *ctx, RedisModuleString *s, double *target, const char *msg); 30 | int GisModule_ExtractUnitOrReply(RedisModuleCtx *ctx, RedisModuleString *unit, double *to_meters); 31 | void GisModule_AddReplyDistance(RedisModuleCtx *ctx, double d); 32 | 33 | #endif // UTIL_H 34 | --------------------------------------------------------------------------------