Asynchronous Client for the worlds fastest in-memory geo-database Tile38

Overview

PYLE38

iwpnd.pw · Report Bug · Request Feature

Table of Contents
  1. About The Project
  2. Getting Started
  3. Commands
  4. Roadmap
  5. Contributing
  6. License
  7. Maintainer
  8. Acknowledgements

About The Project

This is an asynchonous Python client for Tile38 that allows for fast and easy interaction with the worlds fastest in-memory geodatabase Tile38.

Example

import asyncio
from pyle38 import Tile38


async def main():
    tile38 = Tile38(url="redis://localhost:9851", follower_url="redis://localhost:9851")

    await tile38.set("fleet", "truck").point(52.25,13.37).exec()

    response = await tile38.follower()
        .within("fleet")
        .circle(52.25, 13.37, 1000)
        .asObjects()

    assert response.ok

    print(response.dict())

asyncio.run(main())

> {
    "ok": True,
    "elapsed": "48.8µs",
    "objects": [
        {
            "object": {
                "type": "Point",
                "coordinates": [
                    13.37,
                    52.25
                ]
            },
            "id": "truck"
        }
    ],
    "count": 1,
    "cursor": 0
}

Example IPython

In [1]: %autoawait asyncio
In [2]: from pyle38 import Tile38

In [3]: tile38 = Tile38(url='redis://localhost:9851', follower_url='redis://localhost:9852')

In [4]: await tile38.set("fleet", "truck").point(52.25,13.37).exec()
Out[4]: JSONResponse(ok=True, elapsed='51.9µs', err=None)

In [5]: response = await tile38.within("fleet")
   ...:         .circle(52.25, 13.37, 1000)
   ...:         .asObjects()

In [6]: print(response.dict())

  {
    "ok": True,
    "elapsed": "46.3µs",
    "objects": [
        {
            "object": {
                "type": "Point",
                "coordinates": [
                    13.37,
                    52.25
                ]
            },
            "id": "truck"
        }
    ],
    "count": 1,
    "cursor": 0
}

Features

  • fully typed using mypy and pydantic
  • lazy client
  • optional build-in leader/follower logic
  • easy to use and integrate
  • next to no external dependencies

Built With

Getting Started

Requirements

Python==^3.8.2

Installation

pip install pyle38

Or using Poetry

poetry add pyle38

Now start your Tile38 instance(s) either locally using Docker and docker-compose.

docker-compose up

Or follow the installation instruction on tile38.com to start your install Tile38 and start a server locally.

If you already have a Tile38 instance running somewhere read on.

Import

from pyle38 import Tile38
tile38 = Tile38('leader:9851')

Leader / Follower

When it comes to replication, Tile38 follows a leader-follower model. The leader receives all commands that SET data, a follower on the other hand is read-only and can only query data. For more on replication in Tile38 refer to the official documentation.

This client is not meant to setup a replication, because this should happen in your infrastructure. But it helps you to specifically execute commands on leader or follower. This way you can make sure that the leader always has enough resources to execute SETs and fire geofence events on webhooks.

For now you can set one follower url to bet set alongside the leader url.

from pyle38.tile38 import Tile38
tile38 = Tile38('leader:9851', 'follower:9851')

Once the client is instantiated with a follower, commands can be explicitly send to the follower, but adding .follower() to your command chaining.

await tile38.follower().get('fleet', 'truck1').asObjects()

Pagination

Tile38 has hidden limits set for the amount of objects that can be returned in one request. For most queries/searches this limit is set to 100. This client gives you the option to either paginate the results yourself by add .cursor() and .limit() to your queries, or it abstracts pagination away from the user by adding .all().

Let's say your fleet in Berlin extends 100 vehicles, then

await tile38.within('fleet').get('cities', 'Berlin').asObjects()

will only return 100 vehicle objects. Now you can either get the rest by setting the limit to the amount of vehicles you have in the city and get them all.

await tile38.within('fleet').limit(10000).get('cities', 'Berlin').asObjects()

Or, you can paginate the results in multiple concurrent requests to fit your requirements.

await tile38.within('fleet')
    .cursor(0)
    .limit(100)
    .get('cities', 'Berlin')
    .asObjects()

await tile38.within('fleet')
    .cursor(100)
    .limit(100)
    .get('cities', 'Berlin')
    .asObjects()

await tile38.within('fleet')
    .cursor(200)
    .limit(100)
    .get('cities', 'Berlin')
    .asObjects();

Responses

For now, every Tile38 commands response is parsed into a pydantic object for validation and type safety.

response = await tile38.set('fleet','truck1')
                    .point(52.25,13.37)
                    .exec()

print(response.dict())
> {'ok': True, 'elapsed': '40.7µs'}

response = await tile38.get('fleet', 'truck1')

print(response.ok)
> True

print(response.object)
> {
    'type': 'Point',
    'coordinates': [13.37, 52.25]
    }

print(response.dict())
> {
    'ok': True,
    'elapsed': '29.3µs',
    'object': {
        'type': 'Point',
        'coordinates': [13.37, 52.25]
        }
    }

Commands

Keys

SET

Set the value of an id. If a value is already associated to that key/id, it'll be overwritten.

await tile38.set('fleet', 'truck1')
		.fields({ "maxSpeed": 90, "milage": 90000 })
		.point(33.5123, -112.2693)
		.exec()

await tile38.set('fleet', 'truck1:driver').string('John Denton').exec()

Options

.fields() Optional additional fields. MUST BE numerical
.ex(value) Set the specified expire time, in seconds.
.nx() Only set the object if it does not already exist.
.xx() Only set the object if it already exist.

Input

.point(lat, lon) Set a simple point in latitude, longitude
.bounds(minlat, minlon, maxlat, maxlon) Set as minimum bounding rectangle
.object(feature) Set as feature
.hash(geohash) Set as geohash
.string(value) Set as string. To retrieve string values you can use .get(), scan() or .search()

FSET

Set the value for one or more fields of an object. Fields must be double precision floating points. Returns the integer count of how many fields changed their values.

await tile38.fset('fleet', 'truck1', { "maxSpeed": 90, "milage": 90000 })

Options

.xx() FSET returns error if fields are set on non-existing ids. xx() options changes this behaviour and instead returns 0 if id does not exist. If key does not exist FSET still returns error

GET

Get the object of an id.

await tile38.get('fleet', 'truck1')

Options

.withfields() will also return the fields that belong to the object. Field values that are equal to zero are omitted.

Output

.asObject() (default) get as object
.asBounds() get as minimum bounding rectangle
.asHash(precision) get as hash
.asPoint() get as point

DEL

Remove a specific object by key and id.

await tile38.del('fleet', 'truck1')

PDEL

Remove objects that match a given pattern.

await tile38.pDel('fleet', 'truck*')

DROP

Remove all objects in a given key.

await tile38.drop('fleet')

BOUNDS

Get the minimum bounding rectangle for all objects in a given key

await tile38.bounds('fleet')

EXPIRE

Set an expiration/time to live in seconds of an object.

await tile38.expire('fleet', 'truck1', 10)

TTL

Get the expiration/time to live in seconds of an object.

await tile38.ttl('fleet', 'truck1', 10)

PERSIST

Remove an existing expiration/time to live of an object.

await tile38.persist('fleet', 'truck1')

KEYS

Get all keys matching a glob-style-pattern. Pattern defaults to '*'

await tile38.keys()

STATS

Return stats for one or more keys. The returned stats array contains one or more entries, depending on the number of keys in the request.

await tile38.stats('fleet1', 'fleet2')

Returns

in_memory_size estimated memory size in bytes
num_objects objects in the given key
num_points number of geographical objects in the given key

JSET/JSET/JDEL

Set a value in a JSON document. JGET, JSET, and JDEL allow for working with JSON strings

await tile38.jset('user', 901, 'name', 'Tom')
await tile38.jget('user', 901)
> {'name': 'Tom'}

await tile38.jset('user', 901, 'name.first', 'Tom')
await tile38.jset('user', 901, 'name.first', 'Anderson')
await tile38.jget('user', 901)
> {'name': { 'first': 'Tom', 'last': 'Anderson' }}

await tile38.jdel('user', 901, 'name.last')
await tile38.jget('user', 901);
> {'name': { 'first': 'Tom' }}

RENAME

Renames a collection key to newkey.

Options

.nx() Default: false. Changes behavior on how renaming acts if newkey already exists

If the newkey already exists it will be deleted prior to renaming.

await tile38.rename('fleet', 'newfleet', false)

If the newkey already exists it will do nothing.

await tile38.rename('fleet', 'newfleet', true)

Search

Searches are Tile38 bread and butter. They are what make Tile38 a ultra-fast, serious and cheap alternative to PostGIS for a lot of use-cases.

WITHIN

WITHIN searches a key for objects that are fully contained within a given bounding area.

await tile38.within('fleet').bounds(33.462, -112.268,  33.491, -112.245)
> {
   "ok":true,
   "objects":[
      {
         "id":"1",
         "object":{
            "type":"Feature",
            "geometry":{
               "type":"Point",
               "coordinates":[
                  -112.2693,
                  33.5123
               ]
            },
            "properties":{

            }
         }
      }
   ],
   "count":1,
   "cursor":1,
   "elapsed":"72.527µs"
}

await tile38.within('fleet').nofields().asCount()
> {
   "ok":true,
   "count":205,
   "cursor":0,
   "elapsed":"2.078168µs"
}

await tile38.within('fleet').get('warehouses', 'Berlin').asCount();
> {
   "ok":true,
   "count":50,
   "cursor":0,
   "elapsed":"2.078168µs"
}

Options

.cursor(value) used to iterate through your search results. Defaults to 0 if not set explicitly
.limit(value) limit the number of returned objects. Defaults to 100 if not set explicitly
.nofields() if not set and one of the objects in the key has fields attached, fields will be returned. Use this to suppress this behavior and don't return fields.
.match(pattern) Match can be used to filtered objects considered in the search with a glob pattern. .match('truck*') e.g. will only consider ids that start with truck within your key.
.sparse(value) caution seems bugged since Tile38 1.22.6. Accepts values between 1 and 8. Can be used to distribute the results of a search evenly across the requested area.

Outputs

.asObjects() return as array of objects
.asBounds() return as array of minimum bounding rectangles: {"id": str,"bounds":{"sw":{"lat": float,"lon": float},"ne":{"lat": float,"lon": float}}}
.asCount() returns a count of the objects in the search
.asHashes(precision) returns an array of {"id": str,"hash": str}
.asIds() returns an array of ids
.asPoints() returns objects as points: {"id": str,"point":{"lat": float,"lon": float}. If the searched key is a collection of Polygon objects, the returned points are the centroids.

Query

.get(key, id) Search a given stored item in a collection.
.circle(lat, lon, radius) Search a given circle of latitude, longitude and radius.
.bounds(minlat, minlon, maxlat, maxlon) Search a given bounding box.
.hash(value) Search a given geohash.
.quadkey(value) Search a given quadkey
.tile(x, y, z) Search a given tile
.object(value) Search a given GeoJSON polygon feature.

INTERSECTS

Intersects searches a key for objects that are fully contained within a given bounding area, but also returns those that intersect the requested area. When used to search a collection of keys consisting of Point objects (e.g. vehicle movement data) it works like a .within() search as Points cannot intersect. When used to search a collection of keys consisting of Polygon or LineString it also returns objects, that only partly overlap the requested area.

await tile38.intersects('warehouses').hash('u33d').asObjects()

await tile38.intersects('fleet').get('warehouses', 'Berlin').asIds()
> {
   "ok":true,
   "ids":[
      "truck1"
   ],
   "count":1,
   "cursor":0,
   "elapsed":"2.078168ms"
}

Options

.clip() Tells Tile38 to clip returned objects at the bounding box of the requested area. Works with .bounds(), .hash(), .tile() and .quadkey()
.cursor(value) used to iterate through your search results. Defaults to 0 if not set explicitly
.limit(value) limit the number of returned objects. Defaults to 100 if not set explicitly
.nofields() if not set and one of the objects in the key has fields attached, fields will be returned. Use this to suppress this behavior and don't return fields.
.match(pattern) Match can be used to filtered objects considered in the search with a glob pattern. .match('warehouse*') e.g. will only consider ids that start with warehouse within your key.
.sparse(value) caution seems bugged since Tile38 1.22.6. Accepts values between 1 and 8. Can be used to distribute the results of a search evenly across the requested area.

Outputs

.asObjects() return as array of objects
.asBounds() return as array of minimum bounding rectangles: {"id": str,"bounds":{"sw":{"lat": float,"lon": float},"ne":{"lat": float,"lon": float}}}
.asCount() returns a count of the objects in the search
.asHashes(precision) returns an array of {"id": str,"hash": str}
.asIds() returns an array of ids
.asPoints() returns objects as points: {"id": str,"point":{"lat": float,"lon": float}. If the searched key is a collection of Polygon objects, the returned points are the centroids.

Query

.get(key, id) Search a given stored item in a collection.
.circle(lat, lon, radius) Search a given circle of latitude, longitude and radius.
.bounds(minlat, minlon, maxlat, maxlon) Search a given bounding box.
.hash(value) Search a given geohash.
.quadkey(value) Search a given quadkey
.tile(x, y, z) Search a given tile
.object(value) Search a given GeoJSON polygon feature.

Nearby

await tile38.set('fleet', 'truck1')
		.point(33.5123, -112.2693)
		.exec()

await  tile38.nearby('fleet').point(33.5124, -112.2694)
> {
   "ok":true,
   "count":1,
   "cursor":0,
   "elapsed":"42.8µs"
}

await  tile38.nearby('fleet').point(33.5124, -112.2694, 10)
// because truck1 is further away than 10m
> {
   "ok":true,
   "count":0,
   "cursor":0,
   "elapsed":"36µs"
}

Options

.distance() Returns the distance in meters to the object from the query .point()
.cursor(value) used to iterate through your search results. Defaults to 0 if not set explicitly
.limit(value) limit the number of returned objects. Defaults to 100 if not set explicitly
.nofields() if not set and one of the objects in the key has fields attached, fields will be returned. Use this to suppress this behavior and don't return fields.
.match(pattern) Match can be used to filtered objects considered in the search with a glob pattern. .match('warehouse*') e.g. will only consider ids that start with warehouse within your key.
.sparse(value) caution seems bugged since Tile38 1.22.6. Accepts values between 1 and 8. Can be used to distribute the results of a search evenly across the requested area.

Outputs

.asObjects() return as array of objects
.asBounds() return as array of minimum bounding rectangles: {"id": str,"bounds":{"sw":{"lat": float,"lon": float},"ne":{"lat": float,"lon": float}}}
.asCount() returns a count of the objects in the search
.asHashes(precision) returns an array of {"id": str,"hash": str}
.asIds() returns an array of ids
.asPoints() returns objects as points: {"id": str,"point":{"lat": float,"lon": float}. If the searched key is a collection of Polygon objects, the returned points are the centroids.

Query

.point(lat, lon, radius: Optional[int]) Search nearby a given of latitude, longitude. If radius is set, searches nearby the given radius.

Scan

Incrementally iterate through a given collection key.

await  tile38.scan('fleet')

Options

.asc() Values are returned in ascending order. Default if not set.
.desc() Values are returned in descending order.
.cursor(value) used to iterate through your search results. Defaults to 0 if not set explicitly
.limit(value) limit the number of returned objects. Defaults to 100 if not set explicitly
.nofields() if not set and one of the objects in the key has fields attached, fields will be returned. Use this to suppress this behavior and don't return fields.
.match(pattern) Match can be used to filtered objects considered in the search with a glob pattern. .match('warehouse*') e.g. will only consider ids that start with warehouse within your key.

Outputs

.asObjects() return as array of objects
.asBounds() return as array of minimum bounding rectangles: {"id": str,"bounds":{"sw":{"lat": float,"lon": float},"ne":{"lat": float,"lon": float}}}
.asCount() returns a count of the objects in the search
.asHashes(precision) returns an array of {"id": str,"hash": str}
.asIds() returns an array of ids
.asPoints() returns objects as points: {"id": str,"point":{"lat": float,"lon": float}. If the searched key is a collection of Polygon objects, the returned points are the centroids.

Search

Used to iterate through a keys string values.

await tile38.set('fleet', 'truck1:driver').string('John').exec()

await tile38.search('fleet').match('J*').asStringObjects()
> {
   "ok":true,
   "objects":[
      {
         "id":"truck1:driver",
         "object":"John"
      }
   ],
   "count":1,
   "cursor":0,
   "elapsed":"59.9µs"
}

Options

.asc() Values are returned in ascending order. Default if not set.
.desc() Values are returned in descending order.
.cursor(value) used to iterate through your search results. Defaults to 0 if not set explicitly
.limit(value) limit the number of returned objects. Defaults to 100 if not set explicitly
.nofields() if not set and one of the objects in the key has fields attached, fields will be returned. Use this to suppress this behavior and don't return fields.
.match(pattern) Match can be used to filtered objects considered in the search with a glob pattern. .match('J*') e.g. will only consider string values objects that have a string value starting with J

Outputs

.asStringObjects() return as array of objects
.asCount() returns a count of the objects in the search
.asIds() returns an array of ids

Server / Connection

CONFIG GET / REWRITE / SET

While .config_get() fetches the requested configuration, .config_set() can be used to change the configuration.

Important, changes made with .set() will only take affect after .config_rewrite() is used.

Options

requirepass Set a password and make server password-protected, if not set defaults to "" (no password required).
leaderauth If leader is password-protected, followers have to authenticate before they are allowed to follow. Set leaderauth to password of the leader, prior to .follow().
protected-mode Tile38 only allows authenticated clients or connections from localhost. Defaults to: "yes"
maxmemory Set max memory in bytes. Get max memory in bytes/kb/mb/gb.
autogc Set auto garbage collection to time in seconds where server performs a garbage collection. Defaults to: 0 (no garbage collection)
keep_alive Time server keeps client connections alive. Defaults to: 300 (seconds)
await tile38.config_get('keepalive')
> {
   "ok":true,
   "properties":{
      "keepalive":"300"
   },
   "elapsed":"54.6µs"
}

await tile38.config_set('keepalive', 400)
> {"ok":true,"elapsed":"36.9µs"}

await tile38.config_rewrite()
> {"ok":true,"elapsed":"363µs"}

await tile38.config_get('keepalive')
> {
   "ok":true,
   "properties":{
      "keepalive":"400"
   },
   "elapsed":"33.8µs"
}

Advanced options Advanced configuration can not be set with commands, but has to be set in a config file in your data directory. Options above, as well as advanced options can be set and are loaded on start-up.

follow_host URI of Leader to follow
follow_port PORT of Leader to follow
follow_pos ?
follow_id ID of Leader
server_id Server ID of the current instance
read_only Make Tile38 read-only

FLUSHDB

Delete all keys and hooks.

await tile38.flushDb()

GC

Instructs the server to perform a garbage collection.

await tile38.gc()

READONLY

Sets Tile38 into read-only mode. Commands such as.set() and .del() will fail.

await tile38.readonly(True)

SERVER

Get Tile38 statistics.

await tile38.server()

Or get extended statistics:

await tile38.server_extended()

Geofencing

A geofence is a virtual boundary that can detect when an object enters or exits the area. This boundary can be a radius or any search area format, such as a bounding box, GeoJSON object, etc. Tile38 can turn any standard search into a geofence monitor by adding the FENCE keyword to the search.

Geofence events can be:

  • inside (object in specified area),
  • outside (object outside specified area),
  • enter (object enters specified area),
  • exit (object exits specified area),
  • crosses (object that was not in specified area, has enter/exit it).

Geofence events can be send on upon commands:

  • set which sends an event when an object is .set()
  • del which sends a last event when the object that resides in the geosearch is deleted via .del()
  • dropwhich sends a message when the entire collection is dropped

SETHOOK

Creates a webhook that points to a geosearch (NEARBY/WITHIN/INTERSECTS). Whenever a commands creates/deletes/drops an object that fulfills the geosearch, an event is send to the specified endpoint.

# sends event to endpoint, when object in 'fleet'
# enters the area of a 500m radius around
# latitude 33.5123 and longitude -112.2693
await tile38.sethook('warehouse', 'http://10.0.20.78/endpoint')
	    .nearby('fleet')
        .point(33.5123, -112.2693, 500)
        .activate()
await tile38.set('fleet', 'bus').point(33.5123001, -112.2693001).exec()
# results in event =
> {
  "command": "set",
  "group": "5c5203ccf5ec4e4f349fd038",
  "detect": "inside",
  "hook": "warehouse",
  "key": "fleet",
  "time": "2021-03-22T13:06:36.769273-07:00",
  "id": "bus",
  "meta": {},
  "object": { "type": "Point", "coordinates": [-112.2693001, 33.5123001] }
}

Geosearch

.nearby(name, endpoint)
.within(name, endpoint)
.intersects(name, endpoint)

Options

.meta(meta) Optional add additional meta information that are send in the geofence event.
.ex(value) Optional TTL in seconds
.commands(which[]) Select on which command a hook should send an event. Defaults to: ['set', 'del', 'drop']
.detect(what[]) Select what events should be detected. Defaults to: ['enter', 'exit', 'crosses', 'inside', 'outside']

Endpoints

HTTP/HTTPS http:// https:// send messages over HTTP/S. For options see link.
gRPC grpc:// send messages over gRPC. For options see link.
Redis redis:// send messages to Redis. For options see link
Disque disque:// send messages to Disque. For options see link.
Kafka kafka:// send messages to a Kafka topic. For options see link.
AMQP amqp:// send messages to RabbitMQ. For options see link.
MQTT mqtt:// send messages to an MQTT broker. For options see link.
SQS sqs:// send messages to an Amazon SQS queue. For options see link.
NATS nats:// send messages to a NATS topic. For options see link.

SETCHAN / SUBSCRIBE / PSUBSCRIBE

Similar to sethook(), but opens a PUB/SUB channel.

# Start a channel that sends event, when object in 'fleet'
# enters the area of a 500m radius around
# latitude 33.5123 and longitude -112.2693
await tile38.setchan('warehouse', 'http://10.0.20.78/endpoint')
	    .nearby('fleet')
        .point(33.5123, -112.2693, 500)
        .activate()

Given a proper setup of a pubsub channel, every set .set() results in:

await tile38.set('fleet', 'bus')
    .point(33.5123001, -112.2693001)
    .exec();
# event =
> {
  "command": "set",
  "group": "5c5203ccf5ec4e4f349fd038",
  "detect": "inside",
  "hook": "warehouse",
  "key": "fleet",
  "time": "2021-03-22T13:06:36.769273-07:00",
  "id": "bus",
  "meta": {},
  "object": { "type": "Point", "coordinates": [-112.2693001, 33.5123001] }
}

Geosearch

.nearby(name, endpoint)
.within(name, endpoint)
.intersects(name, endpoint)

Options

.meta(meta) Optional addition meta information that a send in the geofence event.
.ex(value) Optional TTL in seconds
.commands(which[]) Select on which command a hook should send an event. Defaults to: ['set', 'del', 'drop']
.detect(what[]) Select what events should be detected. Defaults to: ['enter', 'exit', 'crosses', 'inside', 'outside']

Addition Information

For more information, please refer to:

Roadmap

See the open issues for a list of proposed features (and known issues).

Contributing

Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

License

MIT

Maintainer

Benjamin Ramser - @iwpnd

Project Link: https://github.com/iwpnd/pyle38

Acknowledgements

Josh Baker - maintainer of Tile38

Comments
  • Add ability to pipeline set commands

    Add ability to pipeline set commands

    Feature request

    Add .pipeline() method

    I'm using pyle38 and working on a visualization/simulation app. The ability to pipeline commands would be great.

    question resolved 
    opened by btcpcs 9
  • Error while using SCAN command and async batch SET command( using asyncio.gather(*tasks))

    Error while using SCAN command and async batch SET command( using asyncio.gather(*tasks))

    Problem

    Hello, is there anyone can help me with this problem? I need to excute SCAN command first and then batch insert 100 records. But I receive a python-BaseException error of "json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)" because I get a response with string "OK" for one of the SET command. I do not really understand how that "OK" occours, because I think every SET must return a json response.

    Here are my codes:

    
    import asyncio
    import time
    from pyle38 import Tile38
    
    
    async def scan_data():
        #response = await tile38.follower().scan("scankey").asObjects()
        response = await tile38.scan("scankey").asObjects()
        assert response.ok
        print("scan data end")
        return response.objects
    
    
    async def batch_set():
        tasks = []
        idx = 0
        for _ in range(50):
            tasks.append(tile38.set("testkey", "poi"+str(idx)).point(36, 112).exec())
            idx += 1
        await asyncio.gather(*tasks)
        print("batch set end")
    
    
    async def exec():
        await scan_data()
        await batch_set()
    
    
    if __name__ == '__main__':
        
        tile38 = Tile38(url="redis://127.0.0.1:9851", follower_url="redis://127.0.0.1:9851")
        start = time.time()
        asyncio.run(exec())
        end = time.time()
        print("time =", end-start)
    
        
    

    A thousand thanks.

    bug question 
    opened by junmlai 8
  • How to GET an object of type String?

    How to GET an object of type String?

    Hi, I am having some trouble with get command:

    async def main():
         await tile38.set("car",'car1').string('a').exec()
         await tile38.get("car",'car1').asObject()
    
    asyncio.run(main())
    

    Then I got following error:

    Traceback (most recent call last):
      File "producer.py", line 26, in <module>
        asyncio.run(main())
      File "....\AppData\Local\Programs\Python\Python38\lib\asyncio\runners.py", line 44, in run
        return loop.run_until_complete(main)
      File ".....\AppData\Local\Programs\Python\Python38\lib\asyncio\base_events.py", line 616, in run_until_complete
        return future.result()
      File "producer.py", line 23, in main
        print(await tile38.get("car", 'car1').asObject())
      File "....\AppData\Local\Programs\Python\Python38\lib\site-packages\pyle38\commands\get.py", line 57, in asObject
        return ObjectResponse(**(await self.exec()))
      File "pydantic\main.py", line 406, in pydantic.main.BaseModel.__init__
    pydantic.error_wrappers.ValidationError: 1 validation error for ObjectResponse
    object
      value is not a valid dict (type=type_error.dict)
    
    
    
    
    
    bug question resolved 
    opened by rafalimaz 5
  • chore(deps): bump pydantic from 1.10.2 to 1.10.3

    chore(deps): bump pydantic from 1.10.2 to 1.10.3

    Bumps pydantic from 1.10.2 to 1.10.3.

    Release notes

    Sourced from pydantic's releases.

    v1.10.3 (2022-12-29)

    Full Changelog: https://github.com/pydantic/pydantic/compare/v1.10.2...v1.10.3

    Changelog

    Sourced from pydantic's changelog.

    v1.10.3 (2022-12-29)

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies python 
    opened by dependabot[bot] 2
  • chore(deps-dev): bump python-semantic-release from 7.31.3 to 7.31.4

    chore(deps-dev): bump python-semantic-release from 7.31.3 to 7.31.4

    Bumps python-semantic-release from 7.31.3 to 7.31.4.

    Release notes

    Sourced from python-semantic-release's releases.

    v7.31.4

    Fix

    • Account for trailing newlines in commit messages (#495) (111b151)
    Changelog

    Sourced from python-semantic-release's changelog.

    v7.31.4 (2022-08-23)

    Fix

    • Account for trailing newlines in commit messages (#495) (111b151)
    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies python 
    opened by dependabot[bot] 2
  • chore(deps-dev): bump python-semantic-release from 7.29.6 to 7.30.0

    chore(deps-dev): bump python-semantic-release from 7.29.6 to 7.30.0

    Bumps python-semantic-release from 7.29.6 to 7.30.0.

    Release notes

    Sourced from python-semantic-release's releases.

    v7.30.0

    Feature

    • Add additional_options input for GitHub Action (#477) (aea60e3)

    Fix

    v7.29.7

    Fix

    • Ignore dependency version bumps when parsing version from commit logs (#476) (51bcb78)
    Changelog

    Sourced from python-semantic-release's changelog.

    v7.30.0 (2022-07-25)

    Feature

    • Add additional_options input for GitHub Action (#477) (aea60e3)

    Fix

    v7.29.7 (2022-07-24)

    Fix

    • Ignore dependency version bumps when parsing version from commit logs (#476) (51bcb78)
    Commits
    • 41974a0 7.30.0
    • c9b2514 fix: allow empty additional options (#479)
    • aea60e3 feat: add additional_options input for GitHub Action (#477)
    • db5b425 7.29.7
    • b47a323 style: beautify 51bcb780a9f55fadfaf01612ff65c1f92642c2c1
    • 51bcb78 fix: ignore dependency version bumps when parsing version from commit logs (#...
    • See full diff in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies python 
    opened by dependabot[bot] 2
  • chore(deps): bump aioredis from 2.0.0 to 2.0.1

    chore(deps): bump aioredis from 2.0.0 to 2.0.1

    Bumps aioredis from 2.0.0 to 2.0.1.

    Release notes

    Sourced from aioredis's releases.

    v2.0.1

    Version v2.0.1

    Features

    • Added Python 3.10 to CI & Updated the Docs (see #1160)
    • Enable mypy in CI (see #1101)
    • Synchronized reading the responses from a connection (see #1106)

    Fixes

    • Remove del from Redis (Fixes #1115) (see #1227)
    • fix socket.error raises (see #1129)
    • Fix buffer is closed error when using PythonParser class (see #1213)
    Changelog

    Sourced from aioredis's changelog.

    2.0.1 - (2021-12-20)

    Features

    • Added Python 3.10 to CI & Updated the Docs (see #1160)
    • Enable mypy in CI (see #1101)
    • Synchronized reading the responses from a connection (see #1106)

    Fixes

    • Remove del from Redis (Fixes #1115) (see #1227)
    • fix socket.error raises (see #1129)
    • Fix buffer is closed error when using PythonParser class (see #1213)
    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies python 
    opened by dependabot[bot] 2
  • chore(deps-dev): bump mypy from 0.920 to 0.921

    chore(deps-dev): bump mypy from 0.920 to 0.921

    Bumps mypy from 0.920 to 0.921.

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies python 
    opened by dependabot[bot] 2
  • Not able to set points

    Not able to set points

    Hey,

    Getting below error unknown command OUTPUT, with args beginning with: JSON,

    I am passing the url redis://localhost:6379 while instantiating Tile38(). (tile38 = Tile38(os.environ.get("REDIS_URL")))

    This line works fine. I get error in the just next line of code which is await tile38.set('fleet', id_).point(station[0], station[1]).exec(), where id_ is a string value and station[0] and station[1] is an float value

    My Redis Server is running on localhost:6379

    I tried a lot but was not able to figure out what's going wrong. Can anybody help me with this?

    question 
    opened by GuptaAman08 2
  • chore(deps-dev): bump python-semantic-release from 7.18.0 to 7.19.0

    chore(deps-dev): bump python-semantic-release from 7.18.0 to 7.19.0

    Bumps python-semantic-release from 7.18.0 to 7.19.0.

    Release notes

    Sourced from python-semantic-release's releases.

    v7.19.0

    Feature

    Documentation

    • parser: Documentation for scipy-parser (45ee34a)
    Changelog

    Sourced from python-semantic-release's changelog.

    v7.19.0 (2021-08-16)

    Feature

    Documentation

    • parser: Documentation for scipy-parser (45ee34a)
    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies python 
    opened by dependabot[bot] 2
  • chore(deps): bump aioredis from 2.0.0b1 to 2.0.0

    chore(deps): bump aioredis from 2.0.0b1 to 2.0.0

    Bumps aioredis from 2.0.0b1 to 2.0.0.

    Release notes

    Sourced from aioredis's releases.

    Version v2.0.0

    Version 2.0 is a complete rewrite of aioredis. Starting with this version, aioredis now follows the API of redis-py, so you can easily adapt synchronous code that uses redis-py for async applications with aioredis-py.

    NOTE: This version is not compatible with earlier versions of aioredis. If you upgrade, you will need to make code changes.

    For more details, read our documentation on migrating to version 2.0.

    Changelog

    Sourced from aioredis's changelog.

    2.0.0 - (2021-03-18)

    Features

    • Port redis-py's client implementation to aioredis.
      (see #891)

    • Make hiredis an optional dependency.
      (see #917)

    1.3.1 (2019-12-02)

    Bugfixes

    • Fix transaction data decoding
      (see #657)
    • Fix duplicate calls to pool.wait_closed() upon create_pool() exception.
      (see #671)

    Deprecations and Removals

    • Drop explicit loop requirement in API. Deprecate loop argument. Throw warning in Python 3.8+ if explicit loop is passed to methods.
      (see #666)

    Misc

    1.3.0 (2019-09-24)

    Features

    • Added xdel and xtrim method which missed in commands/streams.py & also added unit test code for them
      (see #438)
    • Add count argument to spop command
      (see #485)
    • Add support for zpopmax and zpopmin redis commands
      (see #550)
    • Add towncrier: change notes are now stored in CHANGES.txt
      (see #576)
    • Type hints for the library
      (see #584)
    • A few additions to the sorted set commands:
    • the blocking pop commands: BZPOPMAX and BZPOPMIN
    • the CH and INCR options of the ZADD command
      (see #618)
    • Added no_ack parameter to xread_group streams method in commands/streams.py

    ... (truncated)

    Commits
    • 6612ba7 Merge pull request #1075 from aio-libs/release-2.0
    • eb9bd17 Change the example install command
    • ef8c278 Restore 3.6 classifier
    • c65e2e7 Auto-update pre-commit hooks (#1077)
    • cf759f7 Remove 3.6 from the list of supported versions
    • 9ad56c8 Use the correct dev status classifier
    • 9cf2643 Bump the version for 2.0.0 release
    • b2952d9 Merge pull request #1068 from aio-libs/missing-lock
    • 3e9d710 Update CHANGES/1068.bugfix
    • ff37ce6 Merge pull request #1074 from aio-libs/fix-health-check
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies python 
    opened by dependabot[bot] 2
Releases(v0.8.0)
  • v0.8.0(Dec 22, 2022)

    As of Tile38 1.30.0 object FIELDs are no longer limited to just numbers and can take any value. On top return values can be filtered using filter-expressions.

    previously:

    await tile38.set('fleet','truck').fields({'speed':10'}).exec()
    await tile38.scan('fleet').where('speed',10,10).asCount()
    

    now:

    await tile38.set('fleet','truck').fields({'motor': {'horse_power':'1000'}}).exec()
    await tile38.scan('fleet').where_expr('motor.horse_power === 1000').asCount()
    

    Please be aware that every field that is not a number, can only be queried with where_expr().

    v0.8.0 (2022-12-22)

    Feature

    • ✨ allow field Any field value (ddc3f4a)
    • ✨ add test helpers (277679b)
    • ✨ implement whereable (65fa065)

    Fix

    Documentation

    Source code(tar.gz)
    Source code(zip)
  • v0.7.0(Sep 6, 2022)

    v0.7.0 (2022-09-06)

    Since aioredis moved into the redis organization and is part of redis-py from 4.2.0 on, I moved pyle38 to redis-py as well.

    Instead of a connection pool pyle38 now uses a single connection client. This fixes #154 where opening addtional connections would lose the state of OUTPUT JSON send to tile38. This would cause responses of new connections to be returned as RESP that would fail on the attempt to parse responses.

    Added side-effect is a 100% speed increase.

    Feature

    • ✨ upgrade to redis-py, use single connection instead of pool (b31136d)
    Source code(tar.gz)
    Source code(zip)
  • v0.6.1(Jan 24, 2022)

    Fixed a minor issue that would not allow Generic types to be used in Pyle38 responses.

    from pyle38.responses import ObjectsResponse
    from pydantic import BaseModel
    
    class Vehicle(BaseModel):
        id: str
    
    vehicles: ObjectsResponse[Vehicle] = await tile38.scan("fleet").asObjects()
    
    # no longer raises
    # Type "Vehicle" cannot be assigned to type variable "[email protected]"    
    # Type "Vehicle" is incompatible with bound type 
    # "Dict[Unknown, Unknown] | str"
    

    v0.6.1 (2022-01-24)

    Fix

    • 🐛 generic types and defaults (45fcd5d)
    Source code(tar.gz)
    Source code(zip)
  • v0.6.0(Jan 4, 2022)

    pyle38

    pyle38 now supports the BUFFER search option introduced in Tile38 v1.27.0. This will create apply a buffer zone to the area format, therefore increasing the search area by x meters in all directions.

    response = await tile38.set("fleet", "bus1").point(52.25,13.37).exec()
    search_area = {
            "type": "Feature",
            "properties": {},
            "geometry": {
                "type": "Polygon",
                "coordinates": [
                    [
                        [13.37009847164154, 52.2498254610514],
                        [13.370516896247862, 52.2498254610514],
                        [13.370516896247862, 52.25017851139772],
                        [13.37009847164154, 52.25017851139772],
                        [13.37009847164154, 52.2498254610514],
                    ]
                ],
            },
        }
    
    response = await tile38.intersects(key).object(search_area).asCount()
    print(response.count)
    # > 0
    
    response = await tile38.intersects(key).buffer(10).object(search_area).asCount()
    print(response.count)
    # > 1
    

    v0.6.0 (2022-01-04)

    Feature

    • ✨ add buffer search option to within search (1ad3655)
    • ✨ add buffer search option to intersects search (f23d800)

    Documentation

    Chore

    • 🐑 update docker-compose.yml to use new json logging introduced in v1.27.0
    • 🐑 update aioredis
    • 🐑 minor changes to typing
    Source code(tar.gz)
    Source code(zip)
  • v0.5.1(Nov 21, 2021)

    Fixed an issue raised in #68 where string objects can be set, but not fetched from Tile38.

    import asyncio
    from pyle38 import Tile38
    
    async def main():
        tile38 = Tile38(url="redis://localhost:9851", follower_url="redis://localhost:9851")
        await tile38.set("fleet", "truck1:driver").string("John").exec()
    
        response = await tile38.get("fleet","truck1:driver").asStringObject()
        assert response.ok
    
        print(response.dict())
    
    asyncio.run(main())
    
    > {
        "ok": True,
        "elapsed": "48.8µs",
        "object": "John" 
    }
    

    Fix

    • 🐛 remove stringobject class in favour of generics (8317d40)
    • 🐛 utilize generic model object (1f95d2c)
    • 🐛 get as string object (9f1a3a3)

    Documentation

    Source code(tar.gz)
    Source code(zip)
  • v0.5.0(Oct 4, 2021)

    Tile38 v1.26.0 introduced a new object typed called sector that can be used in WITHIN and INTERSECTS queries.

    A sector is a circular sector polygon feature spanning the angle between two given bearings, a center point and a radius. A pizza piece!

    image

    import asyncio
    from pyle38 import Tile38
    
    async def main():
        tile38 = Tile38(url="redis://localhost:9851", follower_url="redis://localhost:9851")
    
        await tile38.set("fleet", "truck1").point(52.25,13.37).exec()
    
        response = await tile38.follower()
            .within("fleet")
            .sector(52.25191, 13.37230, 1000, 180, 270) # lat, lon, radius, bearing1, bearing2
            .noFields()
            .asObjects()
    
        assert response.ok
    
        print(response.dict())
    
    asyncio.run(main())
    
    > {
        "ok": True,
        "elapsed": "48.8µs",
        "objects": [
            {
                "object": {
                    "type": "Point",
                    "coordinates": [
                        13.37,
                        52.25
                    ]
                },
                "id": "truck",
            }
        ],
        "count": 1,
        "cursor": 0
    }
    

    v0.5.0 (2021-10-04)

    Feature

    • ✨ add sector search to within and intersects (4b6931b)
    Source code(tar.gz)
    Source code(zip)
  • v0.4.0(Sep 10, 2021)

    Finally got around to add Tile38s WHERE filter.

    Example:

    import asyncio
    from pyle38 import Tile38
    
    async def main():
        tile38 = Tile38(url="redis://localhost:9851", follower_url="redis://localhost:9851")
    
        await tile38.set("fleet", "truck1").fields({"maxspeed":100}).point(52.25,13.37).exec()
        await tile38.set("fleet", "truck2").fields({"maxspeed":80}).point(51.25,12.37).exec()
    
        response = await tile38.follower()
            .within("fleet")
            .where("maxspeed", 100, 100)
            .circle(52.25, 13.37, 100000)
            .asObjects()
    
        assert response.ok
    
        print(response.dict())
    
    asyncio.run(main())
    
    > {
        "ok": True,
        "elapsed": "48.8µs",
        "fields": ["maxspeed"],
        "objects": [
            {
                "object": {
                    "type": "Point",
                    "coordinates": [
                        13.37,
                        52.25
                    ]
                },
                "id": "truck",
                "fields": [100],
            }
        ],
        "count": 1,
        "cursor": 0
    }
    
    

    v0.4.0 (2021-09-10)

    Feature

    • ✨ add where filter to scan command (8c78d8b)
    • ✨ add where filter to search command (5e02bd4)
    • ✨ add where filter to nearby command (0f795c8)
    • ✨ add where filter to intersects command (dbf8be1)
    • ✨ add where filter to within command (ceb5a29)

    Fix

    • 🐛 update server extended response (62628fb)
    Source code(tar.gz)
    Source code(zip)
  • v0.3.2(Jul 31, 2021)

  • v0.3.1(Jul 25, 2021)

  • v0.3.0(Jun 27, 2021)

  • v0.2.0(Jun 12, 2021)

    New Features

    • ✨ add HEALTHZ command to Leader and Follower

    HEALTHZ allows you to check your Tile38 ready state in e.g. a /health endpoint - Read more.

    Refactorings

    • ♻️ bind TypeVar T to dict specifically

    A features properties can now only be of type dict

    Docs

    • 📚️ update readme with HEALTHZ
    • 📚️ update readme with PING
    • 📚️ add docstrings to commands/nearby
    • 📚️ add docstrings to commands/intersects
    • 📚️ add docstrings to commands/within
    Source code(tar.gz)
    Source code(zip)
  • v0.1.0(Apr 8, 2021)

Owner
Ben
geographer turned spatial engineer turned data-something turned software developer
Ben
How to use COG's (Cloud optimized GeoTIFFs) with Rasterio

How to use COG's (Cloud optimized GeoTIFFs) with Rasterio According to Cogeo.org: A Cloud Opdtimized GeoTIFF (COG) is a regular GeoTIFF file, aimed at

Marvin Gabler 8 Jul 29, 2022
Wraps GEOS geometry functions in numpy ufuncs.

PyGEOS PyGEOS is a C/Python library with vectorized geometry functions. The geometry operations are done in the open-source geometry library GEOS. PyG

362 Dec 23, 2022
ProjPicker (projection picker) is a Python module that allows the user to select all coordinate reference systems (CRSs)

ProjPicker ProjPicker (projection picker) is a Python module that allows the user to select all coordinate reference systems (CRSs) whose extent compl

Huidae Cho 4 Feb 06, 2022
Mmdb-server - An open source fast API server to lookup IP addresses for their geographic location

mmdb-server mmdb-server is an open source fast API server to lookup IP addresses

Alexandre Dulaunoy 67 Nov 25, 2022
List of Land Cover datasets in the GEE Catalog

List of Land Cover datasets in the GEE Catalog A list of all the Land Cover (or discrete) datasets in Google Earth Engine. Values, Colors and Descript

David Montero Loaiza 5 Aug 24, 2022
This GUI app was created to show the detailed information about the weather in any city selected by user

WeatherApp Content Brief description Tools Features Hotkeys How it works Screenshots Ways to improve the project Installation Brief description This G

TheBugYouCantFix 5 Dec 30, 2022
iNaturalist observations along hiking trails

This tool reads the route of a hike and generates a table of iNaturalist observations along the trails. It also shows the observations and the route of the hike on a map. Moreover, it saves waypoints

7 Nov 11, 2022
GebPy is a Python-based, open source tool for the generation of geological data of minerals, rocks and complete lithological sequences.

GebPy is a Python-based, open source tool for the generation of geological data of minerals, rocks and complete lithological sequences. The data can be generated randomly or with respect to user-defi

Maximilian Beeskow 16 Nov 29, 2022
Open Data Cube analyses continental scale Earth Observation data through time

Open Data Cube Core Overview The Open Data Cube Core provides an integrated gridded data analysis environment for decades of analysis ready earth obse

Open Data Cube 410 Dec 13, 2022
A package built to support working with spatial data using open source python

EarthPy EarthPy makes it easier to plot and manipulate spatial data in Python. Why EarthPy? Python is a generic programming language designed to suppo

Earth Lab 414 Dec 23, 2022
Python bindings and utilities for GeoJSON

geojson This Python library contains: Functions for encoding and decoding GeoJSON formatted data Classes for all GeoJSON Objects An implementation of

Jazzband 763 Dec 26, 2022
WhiteboxTools Python Frontend

whitebox-python Important Note This repository is related to the WhiteboxTools Python Frontend only. You can report issues to this repo if you have pr

Qiusheng Wu 304 Dec 15, 2022
a Geolocator made in python

Geolocator A Geolocator made in python ✨ Features locates ur location using ur ip thats it! 💁‍♀️ How to use first download the locator.py file instal

Portgas D Ace 1 Oct 27, 2021
A Jupyter - Leaflet.js bridge

ipyleaflet A Jupyter / Leaflet bridge enabling interactive maps in the Jupyter notebook. Usage Selecting a basemap for a leaflet map: Loading a geojso

Jupyter Widgets 1.3k Dec 27, 2022
Geocoding library for Python.

geopy geopy is a Python client for several popular geocoding web services. geopy makes it easy for Python developers to locate the coordinates of addr

geopy 3.8k Dec 30, 2022
Raster-based Spatial Analysis for Python

🌍 xarray-spatial: Raster-Based Spatial Analysis in Python 📍 Fast, Accurate Python library for Raster Operations ⚡ Extensible with Numba ⏩ Scalable w

makepath 649 Jan 01, 2023
Extract GoPro highlights and GPMF data.

Python script that parses the gpmd stream for GOPRO moov track (MP4) and extract the GPS info into a GPX (and kml) file.

Chris Auron 2 May 13, 2022
WebGL2 powered geospatial visualization layers

deck.gl | Website WebGL2-powered, highly performant large-scale data visualization deck.gl is designed to simplify high-performance, WebGL-based visua

Vis.gl 10.5k Jan 08, 2023
Global topography (referenced to sea-level) in a 10 arcminute resolution grid

Earth - Topography grid at 10 arc-minute resolution Global 10 arc-minute resolution grids of topography (ETOPO1 ice-surface) referenced to mean sea-le

Fatiando a Terra Datasets 1 Jan 20, 2022
Geodata extensions for Django REST Framework

Django-Spillway Django and Django REST Framework integration of raster and feature based geodata. Spillway builds on the immensely marvelous Django RE

Brian Galey 62 Jan 04, 2023