{"id":17964805,"url":"https://github.com/windfish-studio/ddrt","last_synced_at":"2026-02-13T10:21:48.376Z","repository":{"id":48176749,"uuid":"210885805","full_name":"windfish-studio/ddrt","owner":"windfish-studio","description":"An elixir implementation of Rtree, optimized for fast updates.","archived":false,"fork":false,"pushed_at":"2023-12-12T09:45:07.000Z","size":228,"stargazers_count":46,"open_issues_count":2,"forks_count":6,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-06-29T11:02:33.573Z","etag":null,"topics":["cluster","distributed","elixir","elixir-processes","erlang","gotta-go-fast","rtree","rtree-range-queries","spatial-data","spatial-index","tree"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-2.1","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/windfish-studio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-09-25T16:01:11.000Z","updated_at":"2024-05-18T05:54:18.000Z","dependencies_parsed_at":"2023-12-19T11:35:14.984Z","dependency_job_id":"19eea19d-09b4-4f3e-bf78-515339e8ad05","html_url":"https://github.com/windfish-studio/ddrt","commit_stats":{"total_commits":65,"total_committers":5,"mean_commits":13.0,"dds":"0.46153846153846156","last_synced_commit":"4f6aa4bcfb76dbbfe19831ff7b62d8266026f898"},"previous_names":["windfish-studio/dynamic-rtree"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/windfish-studio/ddrt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/windfish-studio%2Fddrt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/windfish-studio%2Fddrt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/windfish-studio%2Fddrt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/windfish-studio%2Fddrt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/windfish-studio","download_url":"https://codeload.github.com/windfish-studio/ddrt/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/windfish-studio%2Fddrt/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267046128,"owners_count":24026897,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-25T02:00:09.625Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cluster","distributed","elixir","elixir-processes","erlang","gotta-go-fast","rtree","rtree-range-queries","spatial-data","spatial-index","tree"],"created_at":"2024-10-29T12:08:47.607Z","updated_at":"2025-12-11T23:53:58.312Z","avatar_url":"https://github.com/windfish-studio.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![CircleCI](https://circleci.com/gh/windfish-studio/ddrt.svg?style=svg)](https://circleci.com/gh/windfish-studio/ddrt)\n[![LICENSE](https://img.shields.io/hexpm/l/ddrt)](https://rawcdn.githack.com/windfish-studio/rtree/1479e8660336fb0a63fc6a39185c10e1ab940d7b/LICENSE)\n[![VERSION](https://img.shields.io/hexpm/v/ddrt)](https://hexdocs.pm/ddrt/api-reference.html)\n\n# :ddrt (README)\n\nA Dynamic, Distributed [R-Tree](https://en.wikipedia.org/wiki/R-tree) (__DDRT__) library written in Elixir. The 'dynamic' part of the title refers to the fact that this implementation is optimized for a high volume of update operations. Put another way, this is an R-tree best suited for use with spatial data _in constant movement_. The 'distributed' part refers to the fact that this library is designed to maintain a spatial index (rtree) across a cluster of distributed elixir nodes. \n\nThe library uses [@derekkraan](https://github.com/derekkraan)'s [MerkleMap](https://github.com/derekkraan/merkle_map) and [CRDT](https://github.com/derekkraan/delta_crdt_ex) implementations to ensure reliable, \"eventually consistent\" distributed behavior.\n\nThe complete documentation is [available on hexdocs](https://hexdocs.pm/ddrt). You can find the hex package [here](https://hex.pm/packages/ddrt).\n\n# Getting Started\n## Installation\n\nThe package can be installed\nby adding `ddrt` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:ddrt, \"~\u003e 0.2.1\"}\n  ]\nend\n```\n## Starting DDRT Processes\n\nStart up a DDRT process with default values\n\n```elixir\nDDRT.start_link([\n  name: DDRT\n  width: 6,\n  verbose: false,\n  seed: 0\n])\n```\n\nOr add it to your supervision tree:\n\n```elixir\nSupervisor.start_link([\n  {DDRT, [\n  \tname: DDRT,\n  \twidth: 6,\n  \tverbose: false,\n  \tseed: 0\n  ]}\n], [name: MySupervisor])\n```\n\nOtherwise if you're just looking to use the standalone R-tree functionality on a single machine (not a cluster of machines), you would instead use the `DDRT.DynamicRtree` module:\n\n```elixir\nDDRT.DynamicRtree.start_link([name: DynamicRtree])\n```\n\nNote: all configuration parameters and public API methods are _exactly_ the same between the `DDRT` and `DDRT.DynamicRtree` modules.\n \n## Configuration\n\nAvailable configuration parameters are:\n\n- **name**: The name of the DDRT process. Defaults to `DDRT`\n- **width**: The max number of children a node may have. Defaults to `6`\n- **verbose**: allows `Logger` to report console logs. (Also decreases performance). Defaults to `false`.\n- **seed**: Sets the seed value for the pseudo-random number generator which generates the unique IDs for each node in the tree. This is a deterministic process; so the same seed value will guarantee the same pseudo-random unique IDs being generated for your tree in the same order each time. Defaults to `0`\n\n## Replicating your R-Tree in a cluster\n\nFirst it's important to understand that distributed networking capabilities come built-in with Erlang. To get Elixir processes communicating amongst themselves over a network in general, we first have to use that fundamental Erlang networking magic to make all of the running Erlang Virtual Machines \"aware\" of eachother's existence on the network. In Elixir, these concepts are expressed in the [Node](https://hexdocs.pm/elixir/Node.html) module. One can use `Node.connect/2`\nto make two Erlang VM nodes aware of eachother, and then Elixir processes are able to send messages to eachother on those nodes.\n\n\nConnecting up the Erlang VMs in your cluster is outside of the scope of this package. There are already other libraries in Elixir designed to do exactly this. Possibly the best example is [`bitwalker/libcluster`](https://github.com/bitwalker/libcluster).\n\nA very simple `libcluster` configuration for quick and easy development might look like:\n\n```elixir\n## config.exs ##\n\nuse Mix.Config\nconfig :libcluster,\ntopologies: [\n example: [\n   strategy: Cluster.Strategy.Epmd,\n   config: [hosts: [:\"a@localhost\", :\"b@localhost\"]],\n ]\n]\n```\n\nThen you would have to pass in those same node names to `iex` when you start your application, like:\n\n```elixir\neduardo@ddrt $ iex --name a@localhost -S mix\niex(a@localhost)1\u003e\n\neduardo@ddrt $ iex --name b@localhost -S mix\niex(b@localhost)1\u003e\n```\n\nFinally, after starting DDRT on each node you would use `DDRT.set_members/2` to begin communication between DDRT processes like this:\n\n```elixir\n# on node A:\n{:ok, _pid} = DDRT.start_link([name: DDRT])\nDDRT.set_members(DDRT, [{DDRT, :b@localhost}])\n\n# on node B:\n{:ok, _pid} = DDRT.start_link([name: DDRT])\nDDRT.set_members(DDRT, [{DDRT, :a@localhost}])\n``` \n\nFrom here, everything done on either tree will be reflected in the other tree.\n\nNote: it's important that you have the same configuration parameters for each `DDRT` process running on each connected node in your cluster.\n\n\n# Usage\n\nStarts a local DDRT named `:peter`\n\n```elixir\niex\u003e DDRT.start_link([name: :peter])\n{:ok, #PID\u003c0.214.0\u003e}\n```\n  \nInsert \"Griffin\" into the `:peter` DDRT\n\n```elixir\niex\u003e DDRT.insert({\"Griffin\", [{4,5}, {6,7}]}, :peter)\n{:ok,\n  %{\n    43143342109176739 =\u003e {[\"Griffin\"], nil, [{4, 5}, {6, 7}]},\n    :root =\u003e 43143342109176739,\n    :ticket =\u003e [19125803434255161 | 82545666616502197],\n    \"Griffin\" =\u003e {:leaf, 43143342109176739, [{4, 5}, {6, 7}]}\n}}\n```\n\nInsert \"Parker\" on into the `:peter` DDRT\n\n```elixir\niex\u003e DDRT.insert({\"Parker\",[{10,11},{16,17}]},:peter)\n{:ok,\n  %{\n    43143342109176739 =\u003e {[\"Parker\", \"Griffin\"], nil, [{4, 11}, {6, 17}]},\n    :root =\u003e 43143342109176739,\n    :ticket =\u003e [19125803434255161 | 82545666616502197],\n    \"Griffin\" =\u003e {:leaf, 43143342109176739, [{4, 5}, {6, 7}]},\n    \"Parker\" =\u003e {:leaf, 43143342109176739, [{10, 11}, {16, 17}]}\n}}\n```\n\nQuery which leaves in the `:peter` R-tree overlap with box `[{0,7},{4,8}]`\n\n```elixir\niex\u003e DDRT.query([{0,7},{4,8}],:peter)\n{:ok, [\"Griffin\"]}\n```\n \nUpdates \"Griffin\" bounding box in the `:peter` R-tree\n\n```elixir\niex\u003e DDRT.update(\"Griffin\", [{-6,-5},{11,12}], :peter)\n{:ok,\n  %{\n    43143342109176739 =\u003e {[\"Parker\", \"Griffin\"], nil, [{-6, 11}, {6, 17}]},\n    :root =\u003e 43143342109176739,\n    :ticket =\u003e [19125803434255161 | 82545666616502197],\n    \"Griffin\" =\u003e {:leaf, 43143342109176739, [{-6, -5}, {11, 12}]},\n    \"Parker\" =\u003e {:leaf, 43143342109176739, [{10, 11}, {16, 17}]}\n}}\n```\n\nRepeat the last query again. (This time \"Griffin\" is no longer within the query bounding box.)\n\n```elixir\n iex\u003e DDRT.query([{0,7},{4,8}], :peter)\n {:ok, []}\n```\n  \nNow lets delete both \"Griffin\" and \"Parker\" keys from the tree.\n\n```elixir\niex\u003e DDRT.delete([\"Griffin\",\"Parker\"], :peter)\n{:ok,\n  %{\n    43143342109176739 =\u003e {[], nil, [{0, 0}, {0, 0}]},\n    :root =\u003e 43143342109176739,\n    :ticket =\u003e [19125803434255161 | 82545666616502197]\n}}\n```\n\n### Bounding-Box Format\n\n`[{x_min,x_max}, {y_min,y_max}]`\n\n```elixir\nExample:                               \u0026 \u0026 \u0026 \u0026 \u0026 y_max \u0026 \u0026 \u0026 \u0026 \u0026\n  A unit at pos x: 10, y: -12 ,        \u0026                       \u0026\n  with x_size: 1 and y_size: 2         \u0026                       \u0026\n  would be represented with            \u0026          pos          \u0026\n  the following bounding box         x_min       (x,y)       x_max\n  [{9.5,10.5},{-13,-11}]               \u0026                       \u0026\n                                       \u0026                       \u0026\n                                       \u0026                       \u0026\n                                       \u0026 \u0026 \u0026 \u0026 \u0026 y_min \u0026 \u0026 \u0026 \u0026 \u0026\n```\n\n### Standalone (non-distributed) R-tree mode\n\nIf you're only interested in using an R-tree on a single machine in Elixir, you should be using the `DDRT.DynamicRtree` module. This module is optimized to run on a single machine, and as such the r-tree is significantly faster without the distribution overhead.\n\nThe `DDRT.DynamicRtree` module shares the same API and initialization options as the main `DDRT` module.\n\n## Benchmarks\n\n```elixir\nOperating System: macOS\nCPU Information: Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz\nNumber of Available Cores: 4\nAvailable memory: 8 GB\nElixir 1.9.0\nErlang 22.0.7\n```\n\n### Delete\n```elixir\nBenchmark suite executing with the following configuration:\nwarmup: 2 s\ntime: 5 s\nmemory time: 0 ns\nparallel: 1\ninputs: delete all leaves of tree [1000]\nEstimated total run time: 28 s\n\n##### With input delete all leaves of tree [1000] #####\nName                       ips        average  deviation         median         99th %\nmap bulk                175.20        5.71 ms     ±9.18%        5.60 ms        9.47 ms\nmerklemap bulk           80.27       12.46 ms    ±21.27%       11.74 ms       25.37 ms\nmap 1 by 1                4.68      213.68 ms     ±3.12%      213.24 ms      227.16 ms\nmerklemap 1 by 1          1.55      643.75 ms    ±14.80%      616.84 ms      878.20 ms\n\nComparison: \nmap bulk                175.20\nmerklemap bulk           80.27 - 2.18x slower +6.75 ms\nmap 1 by 1                4.68 - 37.44x slower +207.97 ms\nmerklemap 1 by 1          1.55 - 112.79x slower +638.04 ms\n```\n\n### Update\n```elixir\nBenchmark suite executing with the following configuration:\nwarmup: 2 s\ntime: 10 s\nmemory time: 0 ns\nparallel: 1\ninputs: all leaves of tree [1000], all leaves of tree [100000]\nEstimated total run time: 48 s\n\n##### With input all leaves of tree [1000] #####\nName                ips        average  deviation         median         99th %\nmap              133.88        7.47 ms    ±22.82%        6.92 ms       14.83 ms\nmerklemap         65.74       15.21 ms    ±21.93%       14.18 ms       26.42 ms\n\nComparison: \nmap              133.88\nmerklemap         65.74 - 2.04x slower +7.74 ms\n\n##### With input all leaves of tree [100000] #####\nName                ips        average  deviation         median         99th %\nmap                0.68         1.46 s    ±15.84%         1.47 s         1.82 s\nmerklemap          0.33         3.01 s     ±8.23%         3.09 s         3.21 s\n\nComparison: \nmap                0.68\nmerklemap          0.33 - 2.06x slower +1.55 s\n```\n\n### Query\n```elixir\nBenchmark suite executing with the following configuration:\nwarmup: 2 s\ntime: 5 s\nmemory time: 0 ns\nparallel: 1\ninputs: 100x100 box query, 10x10 box query, 1x1 box query, world box query\nEstimated total run time: 56 s\n\n##### With input 100x100 box query #####\nName                ips        average  deviation         median         99th %\nmerklemap        299.97        3.33 ms    ±28.87%        3.03 ms        6.92 ms\nmap              268.51        3.72 ms    ±36.46%        3.35 ms        8.57 ms\n\nComparison: \nmerklemap        299.97\nmap              268.51 - 1.12x slower +0.39 ms\n\n##### With input 10x10 box query #####\nName                ips        average  deviation         median         99th %\nmap              1.50 K      667.16 μs    ±37.04%         594 μs     1557.56 μs\nmerklemap        1.01 K      992.92 μs    ±48.86%         883 μs     2418.52 μs\n\nComparison: \nmap              1.50 K\nmerklemap        1.01 K - 1.49x slower +325.76 μs\n\n##### With input 1x1 box query #####\nName                ips        average  deviation         median         99th %\nmap              2.01 K      498.54 μs    ±39.28%         430 μs        1257 μs\nmerklemap        1.51 K      660.89 μs    ±45.08%         603 μs     1551.25 μs\n\nComparison: \nmap              2.01 K\nmerklemap        1.51 K - 1.33x slower +162.34 μs\n\n##### With input world box query #####\nName                ips        average  deviation         median         99th %\nmap              156.18        6.40 ms    ±18.51%        5.99 ms       10.70 ms\nmerklemap        152.11        6.57 ms    ±26.12%        5.92 ms       13.93 ms\n\nComparison: \nmap              156.18\nmerklemap        152.11 - 1.03x slower +0.171 ms\n\n```\n\n### Insert\n```elixir\nBenchmark suite executing with the following configuration:\nwarmup: 2 s\ntime: 5 s\nmemory time: 0 ns\nparallel: 1\ninputs: 1000 leaves\nEstimated total run time: 28 s\n\n##### With input 1000 leaves #####\nName                       ips        average  deviation         median         99th %\nmap bulk                305.53        3.27 ms    ±39.39%        2.79 ms        7.96 ms\nmerklemap bulk          190.61        5.25 ms    ±62.06%        4.37 ms       17.65 ms\nmap 1 by 1               66.73       14.99 ms     ±4.63%       14.78 ms       19.11 ms\nmerklemap 1 by 1         23.00       43.48 ms    ±23.79%       39.24 ms       81.16 ms\n\nComparison: \nmap bulk                305.53\nmerklemap bulk          190.61 - 1.60x slower +1.97 ms\nmap 1 by 1               66.73 - 4.58x slower +11.71 ms\nmerklemap 1 by 1         23.00 - 13.28x slower +40.21 ms\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwindfish-studio%2Fddrt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwindfish-studio%2Fddrt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwindfish-studio%2Fddrt/lists"}