Skip to content

Commit a108b4c

Browse files
MTS Incremental Update Public Beta ✨
Add the following commands to support MTS incremental update: - upload-changeset - view-changeset - delete-changeset - publish-changesets
2 parents 67fe4ee + 1ad5a90 commit a108b4c

File tree

11 files changed

+523
-14
lines changed

11 files changed

+523
-14
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ repos:
1515
# E501 let black handle all line length decisions
1616
# W503 black conflicts with "line break before operator" rule
1717
# E203 black conflicts with "whitespace before ':'" rule
18+
# E231 black conflicts with "whitespace after ':'" rule
1819
# E722 bare excepts need to be addressed
19-
'--ignore=E501,W503,E203,E722']
20+
'--ignore=E501,W503,E203,E722,E231']

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
=======
44

5+
# 1.14.0 (2025-04-23)
6+
- Added command `tilesets upload-changeset` that uploads a changeset file.
7+
- Added command `tilesets publish-changesets` that publishes changesets for a incrementally updatable tileset.
8+
- Added command `tilesets view-changeset` that view an uploaded changeset.
9+
- Added command `tilesets delete-changeset` that deletes an uploaded changeset.
10+
511
# 1.13.0 (2025-01-07)
612
- Temporarily remove the `estimate-cu` command for further refinement.
713

README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,12 @@ export MAPBOX_ACCESS_TOKEN=my.token
6565
- Tileset Sources
6666
- [`upload-source`](#upload-source)
6767
- [`upload-raster-source`](#upload-raster-source) (new)
68+
- [`upload-changeset`](#upload-changeset)
6869
- _deprecated_ [`add-source`](#deprecated-add-source)
6970
- [`validate-source`](#validate-source)
7071
- [`view-source`](#view-source)
72+
- [`view-changeset`](#view-changeset)
73+
- [`delete-changeset`](#delete-changeset)
7174
- [`list-sources`](#list-sources)
7275
- [`delete-source`](#delete-source)
7376
- [`estimate-area`](#estimate-area)
@@ -78,6 +81,7 @@ export MAPBOX_ACCESS_TOKEN=my.token
7881
- Tilesets
7982
- [`create`](#create)
8083
- [`publish`](#publish)
84+
- [`publish-changeset`](#publish-changesets)
8185
- [`update`](#update)
8286
- [`delete`](#delete)
8387
- [`status`](#status)
@@ -143,6 +147,23 @@ tilesets upload-raster-source <username> <source_id> ./file.tif
143147
# multiple files
144148
tilesets upload-raster-source <username> <source_id> file-1.tif file-4.tif
145149
```
150+
### upload-changeset
151+
152+
```shell
153+
tilesets upload-changeset <username> <source_id> <file>
154+
```
155+
156+
Uploads GeoJSON files to a changeset for tiling. Accepts line-delimited GeoJSON or GeoJSON feature collections as files or via `stdin`. The CLI automatically converts data to line-delimited GeoJSON prior to uploading. Can be used to add data to a source or to replace all of the data in a source with the `--replace` flag.
157+
158+
Please note that if your source data is a FeatureCollection, `tilesets` must read it all into memory to split it up into separate features before uploading it to the Tilesets API. You are strongly encouraged to provide your data in line-delimited GeoJSON format instead, especially if it is large.
159+
160+
Note: for large file uploads that are taking a very long time, try using the `--no-validation` flag.
161+
162+
Flags:
163+
164+
- `--no-validation` [optional]: do not validate source data locally before uploading, can be helpful for large file uploads
165+
- `--replace` [optional]: delete all existing source data and replace with data from the file
166+
- `--quiet` [optional]: do not display an upload progress bar
146167

147168
### _deprecated_ add-source
148169

@@ -195,6 +216,33 @@ tilesets view-source <username> <source_id>
195216

196217
Get information for a tileset source, such as number of files, the size in bytes, and the ID in mapbox:// protocol format.
197218

219+
### view-changeset
220+
221+
```
222+
tilesets view-changeset <username> <changeset_id>
223+
```
224+
225+
Get information for a changeset, such as number of files, the size in bytes, and the ID in mapbox:// protocol format.
226+
227+
### delete-changeset
228+
229+
```
230+
tilesets delete-changeset <username> <changeset_id>
231+
```
232+
233+
Permanently delete a changeset and all of its files. This is not a recoverable action!
234+
235+
Flags:
236+
237+
- `-f` or `--force`: Do not ask for confirmation before deleting
238+
239+
Usage
240+
241+
```shell
242+
# to delete mapbox://tileset-changeset/user/source_id
243+
tilesets delete-changeset user source_id
244+
```
245+
198246
### list-sources
199247

200248
```
@@ -451,3 +499,12 @@ Flags:
451499
- `--limit [1-500]` [optional]: The maximum number of results to return (default: 100)
452500
- `--indent` [optional]: Indent size for JSON output.
453501
- `--start` [optional]: Pagination key from the `next` value in a response that has more results than the limit.
502+
503+
504+
### publish-changesets
505+
506+
Publishes changesets for an incrementally updatable tileset. This command only supports tilesets created with the [Mapbox Tiling Service](https://docs.mapbox.com/mapbox-tiling-service/overview/).
507+
508+
```shell
509+
tilesets publish-changesets <tileset_id> --changeset /path/to/changeset.json
510+
```

mapbox_tilesets/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""mapbox_tilesets package"""
22

3-
__version__ = "1.13.0"
3+
__version__ = "1.14.0"

mapbox_tilesets/scripts/cli.py

Lines changed: 131 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -543,20 +543,29 @@ def upload_source(
543543
544544
tilesets upload-source <username> <source_id> <path/to/source/data>
545545
"""
546-
return _upload_source(
547-
ctx, username, id, features, no_validation, quiet, replace, token, indent
546+
return _upload_file(
547+
ctx, username, id, features, no_validation, quiet, replace, False, token, indent
548548
)
549549

550550

551-
def _upload_source(
552-
ctx, username, id, features, no_validation, quiet, replace, token=None, indent=None
551+
def _upload_file(
552+
ctx,
553+
username,
554+
id,
555+
features,
556+
no_validation,
557+
quiet,
558+
replace,
559+
changeset,
560+
token=None,
561+
indent=None,
553562
):
563+
api_endpoint = "changesets" if changeset else "sources"
564+
554565
mapbox_api = utils._get_api()
555566
mapbox_token = utils._get_token(token)
556567
s = utils._get_session()
557-
url = (
558-
f"{mapbox_api}/tilesets/v1/sources/{username}/{id}?access_token={mapbox_token}"
559-
)
568+
url = f"{mapbox_api}/tilesets/v1/{api_endpoint}/{username}/{id}?access_token={mapbox_token}"
560569

561570
method = "post"
562571
if replace:
@@ -586,7 +595,7 @@ def _upload_source(
586595
with tempfile.TemporaryFile() as file:
587596
for index, feature in enumerate(features):
588597
if not no_validation:
589-
utils.validate_geojson(index, feature)
598+
utils.validate_geojson(index, feature, changeset)
590599

591600
file.write(
592601
(json.dumps(feature, separators=(",", ":")) + "\n").encode("utf-8")
@@ -749,8 +758,8 @@ def add_source(
749758
750759
tilesets add-source <username> <source_id> <path/to/source/data>
751760
"""
752-
return _upload_source(
753-
ctx, username, id, features, no_validation, quiet, False, token, indent
761+
return _upload_file(
762+
ctx, username, id, features, no_validation, quiet, False, False, token, indent
754763
)
755764

756765

@@ -981,3 +990,115 @@ def list_activity(
981990
click.echo(json.dumps(result, indent=indent))
982991
else:
983992
raise errors.TilesetsError(r.text)
993+
994+
995+
@cli.command("publish-changesets")
996+
@click.argument("tileset_id", required=True, type=str)
997+
@click.argument("changeset_payload", required=True, type=click.Path(exists=True))
998+
@click.option("--token", "-t", required=False, type=str, help="Mapbox access token")
999+
@click.option("--indent", type=int, default=None, help="Indent for JSON output")
1000+
def publish_changesets(tileset_id, changeset_payload, token=None, indent=None):
1001+
"""Publish changesets for a tileset.
1002+
1003+
tilesets publish-changesets <tileset_id> <path_to_changeset_payload>
1004+
"""
1005+
mapbox_api = utils._get_api()
1006+
mapbox_token = utils._get_token(token)
1007+
s = utils._get_session()
1008+
url = "{0}/tilesets/v1/{1}/publish-changesets?access_token={2}".format(
1009+
mapbox_api, tileset_id, mapbox_token
1010+
)
1011+
with open(changeset_payload) as changeset_payload_content:
1012+
changeset_payload_json = json.load(changeset_payload_content)
1013+
1014+
r = s.post(url, json=changeset_payload_json)
1015+
if r.status_code == 200:
1016+
response_msg = r.json()
1017+
click.echo(json.dumps(response_msg, indent=indent))
1018+
else:
1019+
raise errors.TilesetsError(r.text)
1020+
1021+
1022+
@cli.command("view-changeset")
1023+
@click.argument("username", required=True, type=str)
1024+
@click.argument("id", required=True, type=str)
1025+
@click.option("--token", "-t", required=False, type=str, help="Mapbox access token")
1026+
@click.option("--indent", type=int, default=None, help="Indent for JSON output")
1027+
def view_changeset(username, id, token=None, indent=None):
1028+
"""View a Changeset's information
1029+
1030+
tilesets view-changeset <username> <changeset_id>
1031+
"""
1032+
mapbox_api = utils._get_api()
1033+
mapbox_token = utils._get_token(token)
1034+
s = utils._get_session()
1035+
url = "{0}/tilesets/v1/changesets/{1}/{2}?access_token={3}".format(
1036+
mapbox_api, username, id, mapbox_token
1037+
)
1038+
r = s.get(url)
1039+
if r.status_code == 200:
1040+
click.echo(json.dumps(r.json(), indent=indent))
1041+
else:
1042+
raise errors.TilesetsError(r.text)
1043+
1044+
1045+
@cli.command("delete-changeset")
1046+
@click.argument("username", required=True, type=str)
1047+
@click.argument("id", required=True, type=str)
1048+
@click.option("--force", "-f", is_flag=True, help="Circumvents confirmation prompt")
1049+
@click.option("--token", "-t", required=False, type=str, help="Mapbox access token")
1050+
def delete_changeset(username, id, force, token=None):
1051+
"""Permanently delete a changeset and all of its files
1052+
1053+
tilesets delete-changeset <username> <changeset_id>
1054+
"""
1055+
if not force:
1056+
val = click.prompt(
1057+
'To confirm changeset deletion please enter the full changeset id "{0}/{1}"'.format(
1058+
username, id
1059+
),
1060+
type=str,
1061+
)
1062+
if val != f"{username}/{id}":
1063+
raise click.ClickException(
1064+
f"{val} does not match {username}/{id}. Aborted!"
1065+
)
1066+
1067+
mapbox_api = utils._get_api()
1068+
mapbox_token = utils._get_token(token)
1069+
s = utils._get_session()
1070+
url = "{0}/tilesets/v1/changesets/{1}/{2}?access_token={3}".format(
1071+
mapbox_api, username, id, mapbox_token
1072+
)
1073+
r = s.delete(url)
1074+
if r.status_code == 204:
1075+
click.echo("Changeset deleted.")
1076+
else:
1077+
raise errors.TilesetsError(r.text)
1078+
1079+
1080+
@cli.command("upload-changeset")
1081+
@click.argument("username", required=True, type=str)
1082+
@click.argument("id", required=True, callback=validate_source_id, type=str)
1083+
@cligj.features_in_arg
1084+
@click.option("--no-validation", is_flag=True, help="Bypass changeset file validation")
1085+
@click.option("--quiet", is_flag=True, help="Don't show progress bar")
1086+
@click.option(
1087+
"--replace",
1088+
is_flag=True,
1089+
help="Replace the existing changeset with the new changeset file",
1090+
)
1091+
@click.option("--token", "-t", required=False, type=str, help="Mapbox access token")
1092+
@click.option("--indent", type=int, default=None, help="Indent for JSON output")
1093+
@click.pass_context
1094+
def upload_changeset(
1095+
ctx, username, id, features, no_validation, quiet, replace, token=None, indent=None
1096+
):
1097+
"""Create a new changeset, or add data to an existing changeset.
1098+
Optionally, replace an existing changeset.
1099+
1100+
tilesets upload-changeset <username> <source_id> <path/to/changeset/data>
1101+
"""
1102+
return _upload_file(
1103+
ctx, username, id, features, no_validation, quiet, replace, True, token, indent
1104+
)

mapbox_tilesets/utils.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,32 @@ def geojson_validate(index, feature):
8282
)
8383

8484

85-
def validate_geojson(index, feature):
85+
def validate_geojson(index, feature, allow_delete=False):
86+
87+
if allow_delete:
88+
delete_schema = {
89+
"definitions": {},
90+
"$schema": "http://json-schema.org/draft-07/schema#",
91+
"$id": "http://example.com/root.json",
92+
"type": "object",
93+
"title": "GeoJSON Delete Schema",
94+
"required": ["delete"],
95+
"properties": {
96+
"delete": {
97+
"$id": "#/properties/delete",
98+
"const": True,
99+
"title": "The Delete Schema",
100+
"examples": [True],
101+
},
102+
},
103+
}
104+
105+
try:
106+
validate(instance=feature, schema=delete_schema)
107+
return
108+
except:
109+
pass
110+
86111
schema = {
87112
"definitions": {},
88113
"$schema": "http://json-schema.org/draft-07/schema#",
@@ -127,6 +152,7 @@ def validate_geojson(index, feature):
127152
},
128153
},
129154
}
155+
130156
try:
131157
validate(instance=feature, schema=schema)
132158
except ValidationError as e:
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"layers": {
3+
"trees": {
4+
"changeset": "mapbox://tileset-changeset/test.id/test-changeset-id"
5+
}
6+
}
7+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"id": 1, "delete": true, "type": "Feature", "geometry": { "type": "Point","coordinates": [125.6, 10.1]},"properties": {"name": "Dinagat Islands"}}
2+
{"id": 2, "delete": false}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{"id": 1, "type": "Feature", "geometry": { "type": "Point","coordinates": [125.6, 10.1]},"properties": {"name": "Dinagat Islands"}}
2+
{"id": 2, "type": "Feature", "geometry": { "type": "Point","coordinates": [-76.971938, 38.921387]},"properties": {"name": "ZELALEM INJERA"}}
3+
{"id": 3, "delete": true}

0 commit comments

Comments
 (0)