pygeodiff 2.0.4__cp39-cp39-macosx_10_9_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
pygeodiff/__about__.py ADDED
@@ -0,0 +1,10 @@
1
+ __title__ = "PyGeoDiff"
2
+ __description__ = "Diff tool for geo-spatial data"
3
+ __url__ = "https://github.com/MerginMaps/geodiff"
4
+ # use scripts/update_version.py to update the version here and in other places at once
5
+ __version__ = "2.0.4"
6
+ __author__ = "Lutra Consulting Ltd."
7
+ __author_email__ = "info@merginmaps.com"
8
+ __maintainer__ = "Lutra Consulting Ltd."
9
+ __license__ = "MIT"
10
+ __copyright__ = "(c) 2019-2022 Lutra Consulting Ltd."
pygeodiff/__init__.py ADDED
@@ -0,0 +1,19 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ pygeodiff
4
+ -----------
5
+ This module provides tools for create diffs of geospatial data formats
6
+ :copyright: (c) 2019-2022 Lutra Consulting Ltd.
7
+ :license: MIT, see LICENSE for more details.
8
+ """
9
+
10
+ from .main import GeoDiff
11
+ from .geodifflib import (
12
+ GeoDiffLibError,
13
+ GeoDiffLibConflictError,
14
+ GeoDiffLibUnsupportedChangeError,
15
+ GeoDiffLibVersionError,
16
+ ChangesetEntry,
17
+ ChangesetReader,
18
+ UndefinedValue,
19
+ )
@@ -0,0 +1,718 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ pygeodiff.geodifflib
4
+ --------------------
5
+ This module provides wrapper of geodiff C library
6
+ :copyright: (c) 2019-2022 Lutra Consulting Ltd.
7
+ :license: MIT, see LICENSE for more details.
8
+ """
9
+
10
+ import ctypes
11
+ import os
12
+ import platform
13
+ from ctypes.util import find_library
14
+ from .__about__ import __version__
15
+ import copy
16
+
17
+
18
+ class GeoDiffLibError(Exception):
19
+ pass
20
+
21
+
22
+ class GeoDiffLibConflictError(GeoDiffLibError):
23
+ pass
24
+
25
+
26
+ class GeoDiffLibUnsupportedChangeError(GeoDiffLibError):
27
+ pass
28
+
29
+
30
+ class GeoDiffLibVersionError(GeoDiffLibError):
31
+ pass
32
+
33
+
34
+ # keep in sync with c-library
35
+ SUCCESS = 0
36
+ ERROR = 1
37
+ CONFLICT = 2
38
+ UNSUPPORTED_CHANGE = 3
39
+
40
+
41
+ def _parse_return_code(rc, msg):
42
+ if rc == SUCCESS:
43
+ return
44
+ elif rc == ERROR:
45
+ raise GeoDiffLibError(msg)
46
+ elif rc == CONFLICT:
47
+ raise GeoDiffLibConflictError(msg)
48
+ elif rc == UNSUPPORTED_CHANGE:
49
+ raise GeoDiffLibUnsupportedChangeError(msg)
50
+ else:
51
+ raise GeoDiffLibVersionError(
52
+ "Internal error (enum " + str(rc) + " not handled)"
53
+ )
54
+
55
+
56
+ class GeoDiffLib:
57
+ def __init__(self, name):
58
+ self.context = None
59
+ if name is None:
60
+ self.libname = self.package_libname()
61
+ if not os.path.exists(self.libname):
62
+ # not found, try system library
63
+ self.libname = find_library("geodiff")
64
+ else:
65
+ self.libname = name
66
+
67
+ if self.libname is None:
68
+ raise GeoDiffLibVersionError(
69
+ "Unable to locate GeoDiff library, tried "
70
+ + self.package_libname()
71
+ + " and geodiff on system."
72
+ )
73
+
74
+ try:
75
+ self.lib = ctypes.CDLL(self.libname, use_errno=True)
76
+ except OSError:
77
+ raise GeoDiffLibVersionError(
78
+ "Unable to load geodiff library " + self.libname
79
+ )
80
+ self.context = self.init()
81
+ self.callbackLogger = None
82
+ if self.context is None:
83
+ raise GeoDiffLibVersionError("Unable to create GeoDiff context")
84
+
85
+ self.check_version()
86
+ self._register_functions()
87
+
88
+ def __del__(self):
89
+ if self.context is not None:
90
+ func = self.lib.GEODIFF_CX_destroy
91
+ func.argtypes = [ctypes.c_void_p]
92
+ func(self.context)
93
+ self.context = None
94
+
95
+ def _register_functions(self):
96
+ self._readChangeset = self.lib.GEODIFF_readChangeset
97
+ self._readChangeset.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
98
+ self._readChangeset.restype = ctypes.c_void_p
99
+
100
+ # ChangesetReader
101
+ self._CR_nextEntry = self.lib.GEODIFF_CR_nextEntry
102
+ self._CR_nextEntry.argtypes = [
103
+ ctypes.c_void_p,
104
+ ctypes.c_void_p,
105
+ ctypes.c_void_p,
106
+ ]
107
+ self._CR_nextEntry.restype = ctypes.c_void_p
108
+
109
+ self._CR_destroy = self.lib.GEODIFF_CR_destroy
110
+ self._CR_destroy.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
111
+
112
+ # ChangesetEntry
113
+ self._CE_operation = self.lib.GEODIFF_CE_operation
114
+ self._CE_operation.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
115
+ self._CE_operation.restype = ctypes.c_int
116
+
117
+ self._CE_table = self.lib.GEODIFF_CE_table
118
+ self._CE_table.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
119
+ self._CE_table.restype = ctypes.c_void_p
120
+
121
+ self._CE_count = self.lib.GEODIFF_CE_countValues
122
+ self._CE_count.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
123
+ self._CE_count.restype = ctypes.c_int
124
+
125
+ self._CE_old_value = self.lib.GEODIFF_CE_oldValue
126
+ self._CE_old_value.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int]
127
+ self._CE_old_value.restype = ctypes.c_void_p
128
+
129
+ self._CE_new_value = self.lib.GEODIFF_CE_newValue
130
+ self._CE_new_value.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int]
131
+ self._CE_new_value.restype = ctypes.c_void_p
132
+
133
+ self._CE_destroy = self.lib.GEODIFF_CE_destroy
134
+ self._CE_destroy.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
135
+
136
+ # ChangesetTable
137
+ self._CT_name = self.lib.GEODIFF_CT_name
138
+ self._CT_name.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
139
+ self._CT_name.restype = ctypes.c_char_p
140
+
141
+ self._CT_column_count = self.lib.GEODIFF_CT_columnCount
142
+ self._CT_column_count.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
143
+ self._CT_column_count.restype = ctypes.c_int
144
+
145
+ self._CT_column_is_pkey = self.lib.GEODIFF_CT_columnIsPkey
146
+ self._CT_column_is_pkey.argtypes = [
147
+ ctypes.c_void_p,
148
+ ctypes.c_void_p,
149
+ ctypes.c_int,
150
+ ]
151
+ self._CT_column_is_pkey.restype = ctypes.c_bool
152
+
153
+ # Value
154
+ self._V_type = self.lib.GEODIFF_V_type
155
+ self._V_type.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
156
+ self._V_type.restype = ctypes.c_int
157
+
158
+ self._V_get_int = self.lib.GEODIFF_V_getInt
159
+ self._V_get_int.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
160
+ self._V_get_int.restype = ctypes.c_int
161
+
162
+ self._V_get_double = self.lib.GEODIFF_V_getDouble
163
+ self._V_get_double.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
164
+ self._V_get_double.restype = ctypes.c_double
165
+
166
+ self._V_get_data_size = self.lib.GEODIFF_V_getDataSize
167
+ self._V_get_data_size.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
168
+ self._V_get_data_size.restype = ctypes.c_int
169
+
170
+ self._V_get_data = self.lib.GEODIFF_V_getData
171
+ self._V_get_data.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p]
172
+
173
+ self._V_destroy = self.lib.GEODIFF_V_destroy
174
+ self._V_destroy.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
175
+
176
+ def package_libname(self):
177
+ # assume that the package is installed through PIP
178
+ if platform.system() == "Windows":
179
+ prefix = ""
180
+ arch = platform.architecture()[0] # 64bit or 32bit
181
+ if "32" in arch:
182
+ suffix = "-win32.pyd"
183
+ else:
184
+ suffix = ".pyd"
185
+ elif platform.system() == "Darwin":
186
+ prefix = "lib"
187
+ suffix = ".dylib"
188
+ else:
189
+ prefix = "lib"
190
+ suffix = ".so"
191
+ whl_lib = prefix + "pygeodiff-" + __version__ + "-python" + suffix
192
+ dir_path = os.path.dirname(os.path.realpath(__file__))
193
+ return os.path.join(dir_path, whl_lib)
194
+
195
+ def init(self):
196
+ func = self.lib.GEODIFF_createContext
197
+ func.restype = ctypes.c_void_p
198
+ return func()
199
+
200
+ def set_logger_callback(self, callback):
201
+ func = self.lib.GEODIFF_CX_setLoggerCallback
202
+ cFuncType = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
203
+ func.argtypes = [ctypes.c_void_p, cFuncType]
204
+ if callback:
205
+ # do not remove self, callback needs to be member
206
+ self.callbackLogger = cFuncType(callback)
207
+ else:
208
+ self.callbackLogger = cFuncType()
209
+ func(self.context, self.callbackLogger)
210
+
211
+ def set_maximum_logger_level(self, maxLevel):
212
+ func = self.lib.GEODIFF_CX_setMaximumLoggerLevel
213
+ func.argtypes = [ctypes.c_void_p, ctypes.c_int]
214
+ func(self.context, maxLevel)
215
+
216
+ def set_tables_to_skip(self, tables):
217
+ # make array of char* with utf-8 encoding from python list of strings
218
+ arr = (ctypes.c_char_p * len(tables))()
219
+ for i in range(len(tables)):
220
+ arr[i] = tables[i].encode("utf-8")
221
+
222
+ self.lib.GEODIFF_CX_setTablesToSkip(
223
+ ctypes.c_void_p(self.context), ctypes.c_int(len(tables)), arr
224
+ )
225
+
226
+ def version(self):
227
+ func = self.lib.GEODIFF_version
228
+ func.restype = ctypes.c_char_p
229
+ ver = func()
230
+ return ver.decode("utf-8")
231
+
232
+ def check_version(self):
233
+ cversion = self.version()
234
+ pyversion = __version__
235
+ if cversion != pyversion:
236
+ raise GeoDiffLibVersionError(
237
+ "version mismatch ({} C vs {} PY)".format(cversion, pyversion)
238
+ )
239
+
240
+ def drivers(self):
241
+ _driver_count_f = self.lib.GEODIFF_driverCount
242
+ _driver_count_f.argtypes = [ctypes.c_void_p]
243
+ _driver_count_f.restype = ctypes.c_int
244
+
245
+ _driver_name_from_index_f = self.lib.GEODIFF_driverNameFromIndex
246
+ _driver_name_from_index_f.argtypes = [
247
+ ctypes.c_void_p,
248
+ ctypes.c_int,
249
+ ctypes.c_char_p,
250
+ ]
251
+ _driver_name_from_index_f.restype = ctypes.c_int
252
+
253
+ drivers_list = []
254
+ driversCount = _driver_count_f(self.context)
255
+ for index in range(driversCount):
256
+ name_raw = 256 * ""
257
+ b_string1 = name_raw.encode("utf-8")
258
+ res = _driver_name_from_index_f(self.context, index, b_string1)
259
+ _parse_return_code(res, "drivers")
260
+ name = b_string1.decode("utf-8")
261
+ drivers_list.append(name)
262
+
263
+ return drivers_list
264
+
265
+ def driver_is_registered(self, name):
266
+ func = self.lib.GEODIFF_driverIsRegistered
267
+ func.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
268
+ func.restype = ctypes.c_bool
269
+
270
+ b_string1 = name.encode("utf-8")
271
+ return func(self.context, b_string1)
272
+
273
+ def create_changeset(self, base, modified, changeset):
274
+ func = self.lib.GEODIFF_createChangeset
275
+ func.argtypes = [
276
+ ctypes.c_void_p,
277
+ ctypes.c_char_p,
278
+ ctypes.c_char_p,
279
+ ctypes.c_char_p,
280
+ ]
281
+ func.restype = ctypes.c_int
282
+
283
+ # create byte objects from the strings
284
+ b_string1 = base.encode("utf-8")
285
+ b_string2 = modified.encode("utf-8")
286
+ b_string3 = changeset.encode("utf-8")
287
+
288
+ res = func(self.context, b_string1, b_string2, b_string3)
289
+ _parse_return_code(res, "createChangeset")
290
+
291
+ def invert_changeset(self, changeset, changeset_inv):
292
+ func = self.lib.GEODIFF_invertChangeset
293
+ func.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
294
+ func.restype = ctypes.c_int
295
+
296
+ # create byte objects from the strings
297
+ b_string1 = changeset.encode("utf-8")
298
+ b_string2 = changeset_inv.encode("utf-8")
299
+
300
+ res = func(self.context, b_string1, b_string2)
301
+ _parse_return_code(res, "invert_changeset")
302
+
303
+ def create_rebased_changeset(
304
+ self, base, modified, changeset_their, changeset, conflict
305
+ ):
306
+ func = self.lib.GEODIFF_createRebasedChangeset
307
+ func.argtypes = [
308
+ ctypes.c_void_p,
309
+ ctypes.c_char_p,
310
+ ctypes.c_char_p,
311
+ ctypes.c_char_p,
312
+ ctypes.c_char_p,
313
+ ctypes.c_char_p,
314
+ ]
315
+ func.restype = ctypes.c_int
316
+
317
+ # create byte objects from the strings
318
+ b_string1 = base.encode("utf-8")
319
+ b_string2 = modified.encode("utf-8")
320
+ b_string3 = changeset_their.encode("utf-8")
321
+ b_string4 = changeset.encode("utf-8")
322
+ b_string5 = conflict.encode("utf-8")
323
+
324
+ res = func(self.context, b_string1, b_string2, b_string3, b_string4, b_string5)
325
+ _parse_return_code(res, "createRebasedChangeset")
326
+
327
+ def rebase(self, base, modified_their, modified, conflict):
328
+ func = self.lib.GEODIFF_rebase
329
+ func.argtypes = [
330
+ ctypes.c_void_p,
331
+ ctypes.c_char_p,
332
+ ctypes.c_char_p,
333
+ ctypes.c_char_p,
334
+ ctypes.c_char_p,
335
+ ]
336
+ func.restype = ctypes.c_int
337
+
338
+ # create byte objects from the strings
339
+ b_string1 = base.encode("utf-8")
340
+ b_string2 = modified_their.encode("utf-8")
341
+ b_string3 = modified.encode("utf-8")
342
+ b_string4 = conflict.encode("utf-8")
343
+ res = func(self.context, b_string1, b_string2, b_string3, b_string4)
344
+ _parse_return_code(res, "rebase")
345
+
346
+ def apply_changeset(self, base, changeset):
347
+ func = self.lib.GEODIFF_applyChangeset
348
+ func.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
349
+ func.restype = ctypes.c_int
350
+
351
+ # create byte objects from the strings
352
+ b_string1 = base.encode("utf-8")
353
+ b_string2 = changeset.encode("utf-8")
354
+
355
+ res = func(self.context, b_string1, b_string2)
356
+ _parse_return_code(res, "apply_changeset")
357
+
358
+ def list_changes(self, changeset, result):
359
+ func = self.lib.GEODIFF_listChanges
360
+ func.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
361
+ func.restype = ctypes.c_int
362
+
363
+ # create byte objects from the strings
364
+ b_string1 = changeset.encode("utf-8")
365
+ b_string2 = result.encode("utf-8")
366
+ res = func(self.context, b_string1, b_string2)
367
+ _parse_return_code(res, "list_changes")
368
+
369
+ def list_changes_summary(self, changeset, result):
370
+ func = self.lib.GEODIFF_listChangesSummary
371
+ func.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
372
+ func.restype = ctypes.c_int
373
+
374
+ # create byte objects from the strings
375
+ b_string1 = changeset.encode("utf-8")
376
+ b_string2 = result.encode("utf-8")
377
+ res = func(self.context, b_string1, b_string2)
378
+ _parse_return_code(res, "list_changes_summary")
379
+
380
+ def has_changes(self, changeset):
381
+ func = self.lib.GEODIFF_hasChanges
382
+ func.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
383
+ func.restype = ctypes.c_int
384
+
385
+ # create byte objects from the strings
386
+ b_string1 = changeset.encode("utf-8")
387
+
388
+ nchanges = func(self.context, b_string1)
389
+ if nchanges < 0:
390
+ raise GeoDiffLibError("has_changes")
391
+ return nchanges == 1
392
+
393
+ def changes_count(self, changeset):
394
+ func = self.lib.GEODIFF_changesCount
395
+ func.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
396
+ func.restype = ctypes.c_int
397
+
398
+ # create byte objects from the strings
399
+ b_string1 = changeset.encode("utf-8")
400
+
401
+ nchanges = func(self.context, b_string1)
402
+ if nchanges < 0:
403
+ raise GeoDiffLibError("changes_count")
404
+ return nchanges
405
+
406
+ def concat_changes(self, list_changesets, output_changeset):
407
+ # make array of char* with utf-8 encoding from python list of strings
408
+ arr = (ctypes.c_char_p * len(list_changesets))()
409
+ for i in range(len(list_changesets)):
410
+ arr[i] = list_changesets[i].encode("utf-8")
411
+
412
+ res = self.lib.GEODIFF_concatChanges(
413
+ ctypes.c_void_p(self.context),
414
+ ctypes.c_int(len(list_changesets)),
415
+ arr,
416
+ ctypes.c_char_p(output_changeset.encode("utf-8")),
417
+ )
418
+ _parse_return_code(res, "concat_changes")
419
+
420
+ def make_copy(
421
+ self, driver_src, driver_src_info, src, driver_dst, driver_dst_info, dst
422
+ ):
423
+ res = self.lib.GEODIFF_makeCopy(
424
+ ctypes.c_void_p(self.context),
425
+ ctypes.c_char_p(driver_src.encode("utf-8")),
426
+ ctypes.c_char_p(driver_src_info.encode("utf-8")),
427
+ ctypes.c_char_p(src.encode("utf-8")),
428
+ ctypes.c_char_p(driver_dst.encode("utf-8")),
429
+ ctypes.c_char_p(driver_dst_info.encode("utf-8")),
430
+ ctypes.c_char_p(dst.encode("utf-8")),
431
+ )
432
+ _parse_return_code(res, "make_copy")
433
+
434
+ def make_copy_sqlite(self, src, dst):
435
+ res = self.lib.GEODIFF_makeCopySqlite(
436
+ ctypes.c_void_p(self.context),
437
+ ctypes.c_char_p(src.encode("utf-8")),
438
+ ctypes.c_char_p(dst.encode("utf-8")),
439
+ )
440
+ _parse_return_code(res, "make_copy_sqlite")
441
+
442
+ def create_changeset_ex(self, driver, driver_info, base, modified, changeset):
443
+ res = self.lib.GEODIFF_createChangesetEx(
444
+ ctypes.c_void_p(self.context),
445
+ ctypes.c_char_p(driver.encode("utf-8")),
446
+ ctypes.c_char_p(driver_info.encode("utf-8")),
447
+ ctypes.c_char_p(base.encode("utf-8")),
448
+ ctypes.c_char_p(modified.encode("utf-8")),
449
+ ctypes.c_char_p(changeset.encode("utf-8")),
450
+ )
451
+ _parse_return_code(res, "create_changeset_ex")
452
+
453
+ def create_changeset_dr(
454
+ self,
455
+ driver_src,
456
+ driver_src_info,
457
+ src,
458
+ driver_dst,
459
+ driver_dst_info,
460
+ dst,
461
+ changeset,
462
+ ):
463
+ func = self.lib.GEODIFF_createChangesetDr
464
+ func.argtypes = [
465
+ ctypes.c_void_p,
466
+ ctypes.c_char_p,
467
+ ctypes.c_char_p,
468
+ ctypes.c_char_p,
469
+ ctypes.c_char_p,
470
+ ctypes.c_char_p,
471
+ ctypes.c_char_p,
472
+ ctypes.c_char_p,
473
+ ]
474
+ func.restype = ctypes.c_int
475
+
476
+ b_string1 = driver_src.encode("utf-8")
477
+ b_string2 = driver_src_info.encode("utf-8")
478
+ b_string3 = src.encode("utf-8")
479
+ b_string4 = driver_dst.encode("utf-8")
480
+ b_string5 = driver_dst_info.encode("utf-8")
481
+ b_string6 = dst.encode("utf-8")
482
+ b_string7 = changeset.encode("utf-8")
483
+
484
+ res = func(
485
+ self.context,
486
+ b_string1,
487
+ b_string2,
488
+ b_string3,
489
+ b_string4,
490
+ b_string5,
491
+ b_string6,
492
+ b_string7,
493
+ )
494
+ _parse_return_code(res, "CreateChangesetDr")
495
+
496
+ def apply_changeset_ex(self, driver, driver_info, base, changeset):
497
+ res = self.lib.GEODIFF_applyChangesetEx(
498
+ ctypes.c_void_p(self.context),
499
+ ctypes.c_char_p(driver.encode("utf-8")),
500
+ ctypes.c_char_p(driver_info.encode("utf-8")),
501
+ ctypes.c_char_p(base.encode("utf-8")),
502
+ ctypes.c_char_p(changeset.encode("utf-8")),
503
+ )
504
+ _parse_return_code(res, "apply_changeset_ex")
505
+
506
+ def create_rebased_changeset_ex(
507
+ self,
508
+ driver,
509
+ driver_info,
510
+ base,
511
+ base2modified,
512
+ base2their,
513
+ rebased,
514
+ conflict_file,
515
+ ):
516
+ res = self.lib.GEODIFF_createRebasedChangesetEx(
517
+ ctypes.c_void_p(self.context),
518
+ ctypes.c_char_p(driver.encode("utf-8")),
519
+ ctypes.c_char_p(driver_info.encode("utf-8")),
520
+ ctypes.c_char_p(base.encode("utf-8")),
521
+ ctypes.c_char_p(base2modified.encode("utf-8")),
522
+ ctypes.c_char_p(base2their.encode("utf-8")),
523
+ ctypes.c_char_p(rebased.encode("utf-8")),
524
+ ctypes.c_char_p(conflict_file.encode("utf-8")),
525
+ )
526
+ _parse_return_code(res, "create_rebased_changeset_ex")
527
+
528
+ def rebase_ex(self, driver, driver_info, base, modified, base2their, conflict_file):
529
+ res = self.lib.GEODIFF_rebaseEx(
530
+ ctypes.c_void_p(self.context),
531
+ ctypes.c_char_p(driver.encode("utf-8")),
532
+ ctypes.c_char_p(driver_info.encode("utf-8")),
533
+ ctypes.c_char_p(base.encode("utf-8")),
534
+ ctypes.c_char_p(modified.encode("utf-8")),
535
+ ctypes.c_char_p(base2their.encode("utf-8")),
536
+ ctypes.c_char_p(conflict_file.encode("utf-8")),
537
+ )
538
+ _parse_return_code(res, "rebase_ex")
539
+
540
+ def dump_data(self, driver, driver_info, src, changeset):
541
+ res = self.lib.GEODIFF_dumpData(
542
+ ctypes.c_void_p(self.context),
543
+ ctypes.c_char_p(driver.encode("utf-8")),
544
+ ctypes.c_char_p(driver_info.encode("utf-8")),
545
+ ctypes.c_char_p(src.encode("utf-8")),
546
+ ctypes.c_char_p(changeset.encode("utf-8")),
547
+ )
548
+ _parse_return_code(res, "dump_data")
549
+
550
+ def schema(self, driver, driver_info, src, json):
551
+ res = self.lib.GEODIFF_schema(
552
+ ctypes.c_void_p(self.context),
553
+ ctypes.c_char_p(driver.encode("utf-8")),
554
+ ctypes.c_char_p(driver_info.encode("utf-8")),
555
+ ctypes.c_char_p(src.encode("utf-8")),
556
+ ctypes.c_char_p(json.encode("utf-8")),
557
+ )
558
+ _parse_return_code(res, "schema")
559
+
560
+ def read_changeset(self, changeset):
561
+
562
+ b_string1 = changeset.encode("utf-8")
563
+
564
+ reader_ptr = self._readChangeset(self.context, b_string1)
565
+ if reader_ptr is None:
566
+ raise GeoDiffLibError("Unable to open reader for: " + changeset)
567
+ return ChangesetReader(self, reader_ptr)
568
+
569
+ def create_wkb_from_gpkg_header(self, geometry):
570
+ func = self.lib.GEODIFF_createWkbFromGpkgHeader
571
+ func.argtypes = [
572
+ ctypes.c_void_p,
573
+ ctypes.POINTER(ctypes.c_char),
574
+ ctypes.c_size_t,
575
+ ctypes.POINTER(ctypes.POINTER(ctypes.c_char)),
576
+ ctypes.POINTER(ctypes.c_size_t),
577
+ ]
578
+ func.restype = ctypes.c_int
579
+
580
+ out = ctypes.POINTER(ctypes.c_char)()
581
+ out_size = ctypes.c_size_t(len(geometry))
582
+ res = func(
583
+ self.context,
584
+ geometry,
585
+ ctypes.c_size_t(len(geometry)),
586
+ ctypes.byref(out),
587
+ ctypes.byref(out_size),
588
+ )
589
+ _parse_return_code(res, "create_wkb_from_gpkg_header")
590
+ wkb = copy.deepcopy(out[: out_size.value])
591
+ return wkb
592
+
593
+
594
+ class ChangesetReader(object):
595
+ """Wrapper around GEODIFF_CR_* functions from C API"""
596
+
597
+ def __init__(self, geodiff, reader_ptr):
598
+ self.geodiff = geodiff
599
+ self.reader_ptr = reader_ptr
600
+
601
+ def __del__(self):
602
+ self.geodiff._CR_destroy(self.geodiff.context, self.reader_ptr)
603
+
604
+ def next_entry(self):
605
+ ok = ctypes.c_bool()
606
+ entry_ptr = self.geodiff._CR_nextEntry(
607
+ self.geodiff.context, self.reader_ptr, ctypes.byref(ok)
608
+ )
609
+ if not ok:
610
+ raise GeoDiffLibError("Failed to read entry!")
611
+ if entry_ptr is not None:
612
+ return ChangesetEntry(self.geodiff, entry_ptr)
613
+ else:
614
+ return None
615
+
616
+ def __iter__(self):
617
+ return self
618
+
619
+ def __next__(self):
620
+ entry = self.next_entry()
621
+ if entry is not None:
622
+ return entry
623
+ else:
624
+ raise StopIteration
625
+
626
+ next = __next__ # python 2.x compatibility (requires next())
627
+
628
+
629
+ class ChangesetEntry(object):
630
+ """Wrapper around GEODIFF_CE_* functions from C API"""
631
+
632
+ # constants as defined in ChangesetEntry::OperationType enum
633
+ OP_INSERT = 18
634
+ OP_UPDATE = 23
635
+ OP_DELETE = 9
636
+
637
+ def __init__(self, geodiff, entry_ptr):
638
+ self.geodiff = geodiff
639
+ self.entry_ptr = entry_ptr
640
+
641
+ self.operation = self.geodiff._CE_operation(
642
+ self.geodiff.context, self.entry_ptr
643
+ )
644
+ self.values_count = self.geodiff._CE_count(self.geodiff.context, self.entry_ptr)
645
+
646
+ if self.operation == self.OP_DELETE or self.operation == self.OP_UPDATE:
647
+ self.old_values = []
648
+ for i in range(self.values_count):
649
+ v_ptr = self.geodiff._CE_old_value(
650
+ self.geodiff.context, self.entry_ptr, i
651
+ )
652
+ self.old_values.append(self._convert_value(v_ptr))
653
+
654
+ if self.operation == self.OP_INSERT or self.operation == self.OP_UPDATE:
655
+ self.new_values = []
656
+ for i in range(self.values_count):
657
+ v_ptr = self.geodiff._CE_new_value(
658
+ self.geodiff.context, self.entry_ptr, i
659
+ )
660
+ self.new_values.append(self._convert_value(v_ptr))
661
+
662
+ table = self.geodiff._CE_table(self.geodiff.context, entry_ptr)
663
+ self.table = ChangesetTable(geodiff, table)
664
+
665
+ def __del__(self):
666
+ self.geodiff._CE_destroy(self.geodiff.context, self.entry_ptr)
667
+
668
+ def _convert_value(self, v_ptr):
669
+ v_type = self.geodiff._V_type(self.geodiff.context, v_ptr)
670
+ if v_type == 0:
671
+ v_val = UndefinedValue()
672
+ elif v_type == 1:
673
+ v_val = self.geodiff._V_get_int(self.geodiff.context, v_ptr)
674
+ elif v_type == 2:
675
+ v_val = self.geodiff._V_get_double(self.geodiff.context, v_ptr)
676
+ elif v_type == 3 or v_type == 4: # 3==text, 4==blob
677
+ size = self.geodiff._V_get_data_size(self.geodiff.context, v_ptr)
678
+ buffer = ctypes.create_string_buffer(size)
679
+ self.geodiff._V_get_data(self.geodiff.context, v_ptr, buffer)
680
+ v_val = buffer.raw
681
+ if v_type == 3:
682
+ v_val = v_val.decode("utf-8")
683
+ elif v_type == 5:
684
+ v_val = None
685
+ else:
686
+ raise GeoDiffLibError("unknown value type {}".format(v_type))
687
+ self.geodiff._V_destroy(self.geodiff.context, v_ptr)
688
+ return v_val
689
+
690
+
691
+ class ChangesetTable(object):
692
+ """Wrapper around GEODIFF_CT_* functions from C API"""
693
+
694
+ def __init__(self, geodiff, table_ptr):
695
+ self.geodiff = geodiff
696
+ self.table_ptr = table_ptr
697
+
698
+ self.name = self.geodiff._CT_name(self.geodiff.context, table_ptr).decode(
699
+ "utf-8"
700
+ )
701
+ self.column_count = self.geodiff._CT_column_count(
702
+ self.geodiff.context, table_ptr
703
+ )
704
+ self.column_is_pkey = []
705
+ for i in range(self.column_count):
706
+ self.column_is_pkey.append(
707
+ self.geodiff._CT_column_is_pkey(self.geodiff.context, table_ptr, i)
708
+ )
709
+
710
+
711
+ class UndefinedValue(object):
712
+ """Marker that a value in changeset is undefined. This is different
713
+ from NULL value (which is represented as None). Undefined values are
714
+ used for example as values of columns in UPDATE operation that did
715
+ not get modified."""
716
+
717
+ def __repr__(self):
718
+ return "<N/A>"
pygeodiff/main.py ADDED
@@ -0,0 +1,390 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ pygeodiff.main
4
+ --------------
5
+ Main entry of the library
6
+ :copyright: (c) 2019-2022 Lutra Consulting Ltd.
7
+ :license: MIT, see LICENSE for more details.
8
+ """
9
+
10
+ from .geodifflib import GeoDiffLib
11
+
12
+
13
+ class GeoDiff:
14
+ """
15
+ geodiff is a module to create and apply changesets to GIS files (geopackage)
16
+ """
17
+
18
+ def __init__(self, libname=None):
19
+ """
20
+ if libname is None, it tries to import c-extension from wheel
21
+ messages are shown in stdout/stderr.
22
+ Use environment variable GEODIFF_LOGGER_LEVEL 0(Nothing)-4(Debug) to
23
+ set level (Errors by default)
24
+ """
25
+ self.clib = GeoDiffLib(libname)
26
+
27
+ def set_logger_callback(self, callback):
28
+ """
29
+ Assign custom logger
30
+ Replace default stdout/stderr logger with custom.
31
+ When callback is None, no output is produced at all
32
+ Callback function has 2 arguments: (int) errorCode, (string) msg
33
+ """
34
+ return self.clib.set_logger_callback(callback)
35
+
36
+ def set_tables_to_skip(self, tables):
37
+ """
38
+ Set list of tables to exclude from geodiff operations. Once defined, these
39
+ tables will be excluded from the following operations: create changeset,
40
+ apply changeset, rebase, get database schema, dump database contents, copy
41
+ database between different drivers.
42
+
43
+ If empty list is passed, skip tables list will be reset.
44
+ """
45
+ return self.clib.set_tables_to_skip(tables)
46
+
47
+ LevelError = 1
48
+ LevelWarning = 2
49
+ LevelInfo = 3
50
+ LevelDebug = 4
51
+
52
+ def set_maximum_logger_level(self, maxLevel):
53
+ """
54
+ Assign maximum level of messages that are passed to logger callbac
55
+ Based on maxLogLevel, the messages are filtered by level:
56
+ maxLogLevel = 0 nothing is passed to logger callback
57
+ maxLogLevel = 1 errors are passed to logger callback
58
+ maxLogLevel = 2 errors and warnings are passed to logger callback
59
+ maxLogLevel = 3 errors, warnings and infos are passed to logger callback
60
+ maxLogLevel = 4 errors, warnings, infos, debug messages are passed to logger callback
61
+ """
62
+ return self.clib.set_maximum_logger_level(maxLevel)
63
+
64
+ def drivers(self):
65
+ """
66
+ Returns list of registered drivers (e.g. ["sqlite", "postgresql"])
67
+
68
+ raises GeoDiffLibError on error
69
+ """
70
+ return self.clib.drivers()
71
+
72
+ def driver_is_registered(self, name):
73
+ """
74
+ Returns whether dataset with given name is registered (e.g. "sqlite" or "postgresql")
75
+ """
76
+ return self.clib.driver_is_registered(name)
77
+
78
+ def create_changeset(self, base, modified, changeset):
79
+ """
80
+ Creates changeset file (binary) in such way that
81
+ if CHANGESET is applied to BASE by applyChangeset,
82
+ MODIFIED will be created
83
+
84
+ BASE --- CHANGESET ---> MODIFIED
85
+
86
+ \param base [input] BASE sqlite3/geopackage file
87
+ \param modified [input] MODIFIED sqlite3/geopackage file
88
+ \param changeset [output] changeset between BASE -> MODIFIED
89
+
90
+ raises GeoDiffLibError on error
91
+ """
92
+ return self.clib.create_changeset(base, modified, changeset)
93
+
94
+ def invert_changeset(self, changeset, changeset_inv):
95
+ """
96
+ Inverts changeset file (binary) in such way that
97
+ if CHANGESET_INV is applied to MODIFIED by applyChangeset,
98
+ BASE will be created
99
+
100
+ \param changeset [input] changeset between BASE -> MODIFIED
101
+ \param changeset_inv [output] changeset between MODIFIED -> BASE
102
+
103
+ \returns number of conflics
104
+
105
+ raises GeoDiffLibError on error
106
+ """
107
+ return self.clib.invert_changeset(changeset, changeset_inv)
108
+
109
+ def rebase(self, base, modified_their, modified, conflict):
110
+ """
111
+ Rebases local modified version from base to modified_their version
112
+
113
+ --- > MODIFIED_THEIR
114
+ BASE -|
115
+ ----> MODIFIED (local) ---> MODIFIED_THEIR_PLUS_MINE
116
+
117
+ Steps performed on MODIFIED (local) file:
118
+ 1. undo local changes MODIFIED -> BASE
119
+ 2. apply changes from MODIFIED_THEIR
120
+ 3. apply rebased local changes and create MODIFIED_THEIR_PLUS_MINE
121
+
122
+ Note, when rebase is not successfull, modified could be in random state.
123
+ This works in general, even when base==modified, or base==modified_theirs
124
+
125
+ \param base [input] BASE sqlite3/geopackage file
126
+ \param modified_their [input] MODIFIED sqlite3/geopackage file
127
+ \param modified [input] local copy of the changes to be rebased
128
+ \param conflict [output] file where the automatically resolved conflicts are stored. If there are no conflicts, file is not created
129
+
130
+ raises GeoDiffLibError on error
131
+ """
132
+ return self.clib.rebase(base, modified_their, modified, conflict)
133
+
134
+ def create_rebased_changeset(
135
+ self, base, modified, changeset_their, changeset, conflict
136
+ ):
137
+ """
138
+ Creates changeset file (binary) in such way that
139
+ if CHANGESET is applied to MODIFIED_THEIR by
140
+ applyChangeset, the new state will contain all
141
+ changes from MODIFIED and MODIFIED_THEIR.
142
+
143
+ --- CHANGESET_THEIR ---> MODIFIED_THEIR --- CHANGESET ---> MODIFIED_THEIR_PLUS_MINE
144
+ BASE -|
145
+ -----------------------> MODIFIED
146
+
147
+ \param base [input] BASE sqlite3/geopackage file
148
+ \param modified [input] MODIFIED sqlite3/geopackage file
149
+ \param changeset_their [input] changeset between BASE -> MODIFIED_THEIR
150
+ \param changeset [output] changeset between MODIFIED_THEIR -> MODIFIED_THEIR_PLUS_MINE
151
+ \param conflict [output] file where the automatically resolved conflicts are stored. If there are no conflicts, file is not created
152
+
153
+ raises GeoDiffLibError on error
154
+ """
155
+ return self.clib.create_rebased_changeset(
156
+ base, modified, changeset_their, changeset, conflict
157
+ )
158
+
159
+ def apply_changeset(self, base, changeset):
160
+ """
161
+ Applies changeset file (binary) to BASE
162
+
163
+ \param base [input/output] BASE sqlite3/geopackage file
164
+ \param changeset [input] changeset to apply to BASE
165
+ \returns number of conflicts
166
+
167
+ raises GeoDiffLibError on error
168
+ """
169
+ return self.clib.apply_changeset(base, changeset)
170
+
171
+ def list_changes(self, changeset, json):
172
+ """
173
+ Lists changeset content JSON file
174
+ JSON contains all changes in human/machine readable name
175
+ \returns number of changes
176
+
177
+ raises GeoDiffLibError on error
178
+ """
179
+ return self.clib.list_changes(changeset, json)
180
+
181
+ def list_changes_summary(self, changeset, json):
182
+ """
183
+ Lists changeset summary content JSON file
184
+ JSON contains a list of how many inserts/edits/deletes is contained in changeset for each table
185
+ \returns number of changes
186
+
187
+ raises GeoDiffLibError on error
188
+ """
189
+ return self.clib.list_changes_summary(changeset, json)
190
+
191
+ def has_changes(self, changeset):
192
+ """
193
+ \returns whether changeset contains at least one change
194
+
195
+ raises GeoDiffLibError on error
196
+ """
197
+ return self.clib.has_changes(changeset)
198
+
199
+ def changes_count(self, changeset):
200
+ """
201
+ \returns number of changes
202
+
203
+ raises GeoDiffLibError on error
204
+ """
205
+ return self.clib.changes_count(changeset)
206
+
207
+ def concat_changes(self, list_changesets, output_changeset):
208
+ """
209
+ Combine multiple changeset files into a single changeset file. When the output changeset
210
+ is applied to a database, the result should be the same as if the input changesets were applied
211
+ one by one. The order of input files is important. At least two input files need to be
212
+ provided.
213
+
214
+ Incompatible changes (which would cause conflicts when applied) will be discarded.
215
+
216
+ raises GeoDiffLibError on error
217
+ """
218
+ return self.clib.concat_changes(list_changesets, output_changeset)
219
+
220
+ def make_copy(
221
+ self, driver_src, driver_src_info, src, driver_dst, driver_dst_info, dst
222
+ ):
223
+ """
224
+ Makes a copy of the source dataset (a collection of tables) to the specified destination.
225
+
226
+ This will open the source dataset, get list of tables, their structure, dump data
227
+ to a temporary changeset file. Then it will create the destination dataset, create tables
228
+ and insert data from changeset file.
229
+
230
+ Supported drivers:
231
+
232
+ - "sqlite" - does not need extra connection info (may be null). A dataset is a single Sqlite3
233
+ database (a GeoPackage) - a path to a local file is expected.
234
+
235
+ - "postgres" - only available if compiled with postgres support. Needs extra connection info
236
+ argument which is passed to libpq's PQconnectdb(), see PostgreSQL docs for syntax.
237
+ A datasource identifies a PostgreSQL schema name (namespace) within the current database.
238
+
239
+ raises GeoDiffLibError on error
240
+ """
241
+ return self.clib.make_copy(
242
+ driver_src, driver_src_info, src, driver_dst, driver_dst_info, dst
243
+ )
244
+
245
+ def make_copy_sqlite(self, src, dst):
246
+ """
247
+ Makes a copy of a SQLite database. If the destination database file exists, it will be overwritten.
248
+
249
+ This is the preferred way of copying SQLite/GeoPackage files compared to just using raw copying
250
+ of files on the file system: it will take into account other readers/writers and WAL file,
251
+ so we should never end up with a corrupt copy.
252
+
253
+ raises GeoDiffLibError on error
254
+ """
255
+ return self.clib.make_copy_sqlite(src, dst)
256
+
257
+ def create_changeset_ex(self, driver, driver_info, base, modified, changeset):
258
+ """
259
+ This is an extended version of create_changeset() which also allows specification
260
+ of the driver and its extra connection info. The original create_changeset() function
261
+ only supports Sqlite driver.
262
+
263
+ See documentation of make_copy() for details about supported drivers.
264
+
265
+ raises GeoDiffLibError on error
266
+ """
267
+ return self.clib.create_changeset_ex(
268
+ driver, driver_info, base, modified, changeset
269
+ )
270
+
271
+ def create_changeset_dr(
272
+ self,
273
+ driver_src,
274
+ driver_src_info,
275
+ src,
276
+ driver_dst,
277
+ driver_dst_info,
278
+ dst,
279
+ changeset,
280
+ ):
281
+ """
282
+ Creates changeset file (binary) between src and dest for different drivers.
283
+ Currently supported drivers:
284
+ - sqlite
285
+ - postgres
286
+
287
+ See documentation of create_changeset for more information about changeset.
288
+
289
+ \param driver_src [input] driver of base src
290
+ \param driver_src_info [input] connection string, leave empty for sqlite, for postgres pass a string of format:
291
+ "host=<host> port=<port> user=<user> password=<password> dbname=<database name>"
292
+ \param src [input] BASE sqlite3/geopackage file for sqlite and schema name for postgres
293
+ \param driver_dst [input] driver of modified dst
294
+ \param driver_dst_info [input] connection string for destination driver
295
+ \param dst [input] MODIFIED sqlite3/geopackage file for sqlite and schema name for postgres
296
+ \param changeset [output] changeset between SRC -> DST
297
+
298
+ raises GeoDiffLibError on error
299
+ """
300
+ return self.clib.create_changeset_dr(
301
+ driver_src,
302
+ driver_src_info,
303
+ src,
304
+ driver_dst,
305
+ driver_dst_info,
306
+ dst,
307
+ changeset,
308
+ )
309
+
310
+ def apply_changeset_ex(self, driver, driver_info, base, changeset):
311
+ """
312
+ This is an extended version of apply_changeset() which also allows specification
313
+ of the driver and its extra connection info. The original apply_changeset() function
314
+ only supports Sqlite driver.
315
+
316
+ See documentation of make_copy() for details about supported drivers.
317
+
318
+ raises GeoDiffLibError on error
319
+ """
320
+ return self.clib.apply_changeset_ex(driver, driver_info, base, changeset)
321
+
322
+ def create_rebased_changeset_ex(
323
+ self,
324
+ driver,
325
+ driver_info,
326
+ base,
327
+ base2modified,
328
+ base2their,
329
+ rebased,
330
+ conflict_file,
331
+ ):
332
+ """
333
+ This function takes an existing changeset "base2modified" and rebases it on top of changes in
334
+ "base2their" and writes output to a new changeset "rebased"
335
+
336
+ raises GeoDiffLibError on error
337
+ """
338
+ return self.clib.create_rebased_changeset_ex(
339
+ driver, driver_info, base, base2modified, base2their, rebased, conflict_file
340
+ )
341
+
342
+ def rebase_ex(self, driver, driver_info, base, modified, base2their, conflict_file):
343
+ """
344
+ This function takes care of updating "modified" dataset by taking any changes between "base"
345
+ and "modified" datasets and rebasing them on top of base2their changeset.
346
+
347
+ raises GeoDiffLibError on error
348
+ """
349
+ return self.clib.rebase_ex(
350
+ driver, driver_info, base, modified, base2their, conflict_file
351
+ )
352
+
353
+ def dump_data(self, driver, driver_info, src, changeset):
354
+ """
355
+ Dumps all data from the data source as INSERT statements to a new changeset file.
356
+
357
+ raises GeoDiffLibError on error
358
+ """
359
+ return self.clib.dump_data(driver, driver_info, src, changeset)
360
+
361
+ def schema(self, driver, driver_info, src, json):
362
+ """
363
+ Writes a JSON file containing database schema of tables as understood by geodiff.
364
+
365
+ raises GeoDiffLibError on error
366
+ """
367
+ return self.clib.schema(driver, driver_info, src, json)
368
+
369
+ def read_changeset(self, changeset):
370
+ """
371
+ Opens a changeset file and returns reader object or raises GeoDiffLibError on error.
372
+ """
373
+ return self.clib.read_changeset(changeset)
374
+
375
+ def version(self):
376
+ """
377
+ geodiff version
378
+ """
379
+ return self.clib.version()
380
+
381
+ def create_wkb_from_gpkg_header(self, geometry):
382
+ """
383
+ Extracts geometry in WKB format from the geometry encoded according to GeoPackage spec
384
+ """
385
+ return self.clib.create_wkb_from_gpkg_header(geometry)
386
+
387
+
388
+ def main():
389
+ diff_lib = GeoDiff()
390
+ print("pygeodiff " + diff_lib.version())
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Lutra Consulting Ltd.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.1
2
+ Name: pygeodiff
3
+ Version: 2.0.4
4
+ Summary: Python wrapper around GeoDiff library
5
+ Home-page: https://github.com/MerginMaps/geodiff
6
+ Author: Lutra Consulting Ltd.
7
+ Author-email: info@merginmaps.com
8
+ License: License :: OSI Approved :: MIT License
9
+ Keywords: diff,gis,geo,geopackage,merge
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.7
12
+ License-File: LICENSE
13
+
14
+ Python wrapper around GeoDiff library
@@ -0,0 +1,12 @@
1
+ pygeodiff/__init__.py,sha256=tNR2dfUOhbLnHY9BFg3SXsFqT-MVjU5WlSjePwFFaas,470
2
+ pygeodiff/libpygeodiff-2.0.4-python.dylib,sha256=dxMQX0bKeCJ7_xR2bpiQvAOumQvX3IgHNaIPS6sZ3bg,528848
3
+ pygeodiff/__about__.py,sha256=XRBs6-H8pn1zorWiCWgu5u1cBQwk1SCWmgaNiNEC-NI,427
4
+ pygeodiff/geodifflib.py,sha256=BfAK0K4t7rMgJ13yiH1Zl1OPdUATu5JBzHTcn05NZ2E,25086
5
+ pygeodiff/main.py,sha256=PE0dFuDfsMC2gVEjd5lEqjDdf7uHLh5DTdVIJzstgeQ,14423
6
+ pygeodiff/.dylibs/libsqlite3.0.dylib,sha256=EuFr3TE5NVNAn3v_oZGQf5egds6bmegFQ_Y-FW0nrwo,1793696
7
+ pygeodiff-2.0.4.dist-info/RECORD,,
8
+ pygeodiff-2.0.4.dist-info/LICENSE,sha256=pPekwnp737i2BfjOCth9EjHA8ijKqgUqnZvXhSiy7rk,1078
9
+ pygeodiff-2.0.4.dist-info/WHEEL,sha256=vmRTWwSr0k67XZG4u7Ipy0i2UTgz-efRH2vDqHyId_k,103
10
+ pygeodiff-2.0.4.dist-info/entry_points.txt,sha256=RT77c6_7NNim8cyF-SS9JxyG_28rsFv-Me24fU2co7o,50
11
+ pygeodiff-2.0.4.dist-info/top_level.txt,sha256=ndjfGeE8jGvELbnBQHaHjf13GDrUcOGA7V64GnQvFgc,10
12
+ pygeodiff-2.0.4.dist-info/METADATA,sha256=JGHSMWwWACuAbCoMFBTzEhOc70wEBBGVrpgOkOhgtkk,433
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: skbuild 0.18.1
3
+ Root-Is-Purelib: false
4
+ Tag: cp39-cp39-macosx_10_9_x86_64
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pygeodiff = pygeodiff.main:main
@@ -0,0 +1 @@
1
+ pygeodiff