{"id":18542199,"url":"https://github.com/heremaps/flexible-polyline","last_synced_at":"2025-04-04T11:16:45.282Z","repository":{"id":41203158,"uuid":"222882755","full_name":"heremaps/flexible-polyline","owner":"heremaps","description":"Flexible Polyline encoding: a lossy compressed representation of a list of coordinate pairs or triples","archived":false,"fork":false,"pushed_at":"2024-03-14T14:31:08.000Z","size":9267,"stargazers_count":84,"open_issues_count":20,"forks_count":72,"subscribers_count":13,"default_branch":"master","last_synced_at":"2024-04-14T05:58:16.908Z","etag":null,"topics":["polyline","polyline-decoder","polyline-encoder"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/heremaps.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-11-20T08:01:41.000Z","updated_at":"2024-05-10T15:27:00.591Z","dependencies_parsed_at":"2024-05-10T15:26:59.701Z","dependency_job_id":"e427388a-3a98-44ec-a0d9-48ee587b010c","html_url":"https://github.com/heremaps/flexible-polyline","commit_stats":{"total_commits":53,"total_committers":18,"mean_commits":"2.9444444444444446","dds":0.7924528301886793,"last_synced_commit":"3e1ebc2e2ce39111b0e2e91260b98053ac965f77"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heremaps%2Fflexible-polyline","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heremaps%2Fflexible-polyline/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heremaps%2Fflexible-polyline/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heremaps%2Fflexible-polyline/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/heremaps","download_url":"https://codeload.github.com/heremaps/flexible-polyline/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246936331,"owners_count":20857576,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["polyline","polyline-decoder","polyline-encoder"],"created_at":"2024-11-06T20:07:43.987Z","updated_at":"2025-04-04T11:16:45.259Z","avatar_url":"https://github.com/heremaps.png","language":"Rust","readme":"# Flexible Polyline encoding\n\nThe flexible polyline encoding is a lossy compressed representation of a list of coordinate pairs or\ncoordinate triples.\n\nIt achieves that by:\n\n1. Reducing the decimal digits of each value.\n2. Encoding only the offset from the previous point.\n3. Using variable length for each coordinate delta.\n4. Using 64 URL-safe characters to display the result.\n\nThe encoding is a variant of [Encoded Polyline Algorithm Format]. The advantage of this encoding\nover the original are the following:\n\n* Output string is composed by only URL-safe characters, i.e. may be used without URL encoding as \n  query parameters.\n* Floating point precision is configurable: This allows to represent coordinates with\n  precision up to microns (5 decimal places allow meter precision only).\n* It allows to encode a 3rd dimension with a given precision, which may be a level, altitude, \n  elevation or some other custom value.\n\n## Specifications\n\nAn encoded flexible polyline is composed by two main parts: A header and the actual polyline data. \nThe header always starts with a version number that refers to the specifications in use. A change in \nthe version may affect the logic to encode and decode the rest of the header and data. v.1 is the \nonly version currently defined and this is the version assumed in the rest of the document.\n\n```[header version][header content][data]```\n\n### Encoding\n\nBoth header and data make use of variable length integer encoding.\n\nEvery input integer is converted in one or more chunks of 6 bits where the highest bit is a control \nbit while the remaining five store actual data. Each of these chunks gets encoded separately as a \nprintable character using the following character set:\n\n```ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_```\n\nWhere `A` represents 0  and `_` represents 63.\n\n#### Encoding an unsigned integer\n\nThe variable encoding uses the highest bit (the sixth in this case) as control bit: When it's set to \n`1` it means another chunk needs to be read. Here is the algorithm to encode an unsigned integer:\n\n1. Given the binary representation of the number, split it in chunks of 5 bits.\n2. Convert every chunk from right to left.\n 1. Every chunk that is followed by another chunk will be in OR with `0x20` (set the sixth bit to \n    `1`) and then encoded as a character.\n 2. The last chunk (leftmost) is encoded as a character directly since the sixth bit is already set \n    to `0`.\n\n#### Encoding a signed integer\n\nIn the binary representation of signed integers, the least significant bit stores the sign and the \nsubsequent bits encode the number value resp. `abs(n)-1` for `n \u003c 0`. That is, a positive integer \n`p` is stored as `2*p` while a negative integer `n` is stored as `2*abs(n)-1`. So for example, the \nnumber 7 is represented as `1110` and -7 as `1101`. After this transformation, the normal unsigned \ninteger encoding algorithm can be used.\n\n\n### Header version\n\nThe first unsigned varint of the encoded string refers to the specification version.\n\n### Header content\n\nIt's encoded as an unsigned varint. The bits of the header content have the following structure:\n\n```\nbit   [10              7] [6          4] [3       0]\nvalue [3rd dim precision] [3rd dim flag] [precision]\n```\n\n#### precision\n\nRefers to the precision (decimal digits after the comma) of the latitude and longitude coordinates. \nIt is encoded as an unsigned integer with a range 0 to 15.\n\n#### 3rd dim flag\n\nThis flag specifies whether the third dimension is present and what meaning it has. It's encoded as \nan unsigned integer with a range 0 to 7.\n\nPossible values are:\n\n  * 0 – absent\n  * 1 – level\n  * 2 – altitude\n  * 3 – elevation\n  * 4 – *reserved1*\n  * 5 – *reserved2*\n  * 6 – custom1\n  * 7 – custom2\n\nNote that the concrete unit used for the 3rd dimension is not specified, and depends on how the format is used.\nIt is recommended to make sure it is properly documented (e.g. elevation could be represented in WGS84 or EGM96).\n\n\n#### 3rd dim precision\n\nRefers to the precision (decimal digits after the comma) of the third dimension. Possible values are \n0 to 15.\n\n### Data\n\nAll data values need to be normalized before encoding by transforming them to integers with the \ngiven precision. For example if precision is 5, the value 12.3 becomes 1230000.\n\nThe data section is composed by a sequence of signed varints grouped in tuples of the same size (2 \nif the 3rd dimension flag is absent and 3 otherwise).\n\nThe first tuple contains the first set of normalized coordinates, while any other subsequent tuple \ncontains the offset between two consecutive values on the same dimension.\n\n```Lat0 Lng0 3rd0 (Lat1-Lat0) (Lng1-Lng0) (3rdDim1-3rdDim0) ...```\n\n## Example\n\nThe following coordinates\n\n```\n(50.10228, 8.69821), (50.10201, 8.69567), (50.10063, 8.69150), (50.09878, 8.68752)\n```\n\nare encoded with precision 5 as follows:\n\n```\nB F oz5xJ 67i1B 1B 7P zI ha xL 7Y\n```\n\n* The initial `B` is the header version (v.1)\n* The second letter `F` is the header, corresponding to the precision 5 and no 3rd dimension set.\n* The rest of the string are the encoded coordinates.\n\n## Pseudocode\n\nThe following pseudocode illustrates the steps needed to decode an encoded string and\nmight also be a helpful template for an actual implementation. Note that error handling\nis not taken care of in the code.\n\nThe first function that is needed for a decoder is retuning the index of a single\ncharacter in the character set. E.g. for the first character 'A' it should return 0.\n\nThis can be implemented in an efficient way by converting the character into its\nASCII-code and using this as an index in a decoding table.\nE.g. for the character 'A' the ASCII code is 65, so the decoding table should be\ninitialized with a 0 at this index.\n\n```commandline\nfunction decode_integer\n    input: a character from the encoded string\n    output: the index of the character in the character set\n\n    DECODING_TABLE := [\n        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n        -1, -1, -1, -1, -1, 62, -1, -1, 52, 53,\n        54, 55, 56, 57, 58, 59, 60, 61, -1, -1,\n        -1, -1, -1, -1, -1,  0,  1,  2,  3,  4,\n         5,  6,  7,  8,  9, 10, 11, 12, 13, 14,\n        15, 16, 17, 18, 19, 20, 21, 22, 23, 24,\n        25, -1, -1, -1, -1, 63, -1, 26, 27, 28,\n        29, 30, 31, 32, 33, 34, 35, 36, 37, 38,\n        39, 40, 41, 42, 43, 44, 45, 46, 47, 48,\n        49, 50, 51\n    ]\n\n    return DECODING_TABLE[asci_code(charater)]\n\nend_function\n```\n\nNow the decoding of the first two header bytes can be implemented:\n\n```commandline\nfunction decode_header\n    input: encoded_string\n    output: a tuple of 3 values (precision_2d, type_3d, precision_3d)\n\n    header_version = decode_integer(encoded_string[0])\n    header_content = decode_integer(encoded_string[1])\n\n    precision_2d = header_content \u0026 0xF\n    type_3d = (header_content \u003e\u003e 4) \u0026 0x7\n    precision_3d = (header_content \u003e\u003e 7) \u0026 0xF\n\n    return Tuple(precision_2d, type_3d, precision_3d)\n\nend_function\n```\n\nAfter the two header bytes the encoded coordinates start. More precisely,\nthe sequence of signed coordinate deltas in integer space.\n\nThe function below decodes these bytes into a sequence of signed integer values:\n\n```commandline\nfunction decode_signed_deltas\n    input: encoded_deltas (= encoded string without first two header bytes)\n    output: sequence of signed integer values\n\n    values := array()\n    next_value := 0\n    shift := 0\n    for character in encoded_deltas\n        chunk := decode_integer(character)\n        is_last_chunk := (chunk \u0026 0x20) == 0\n        chunk_value := chunk \u0026 0x1F\n\n        # prepend the chunk value to next_value:\n        next_value := (chunk_value \u003c\u003c shift) | next_value\n        shift := shift + 5\n\n        if is_last_chunk\n            # Convert chunk_value to a signed integer:\n            if next_value \u0026 1 == 1  # if first bit is 1, value is negative\n                signed_value := - ((next_value + 1) \u003e\u003e 1)\n            else\n                signed_value := next_value \u003e\u003e 1\n            end_if\n            values.append(signed_value)\n            next_value := 0\n            shift := 0\n        end_if\n\n    end_for\n\n    return values\n\nend_function\n```\n\nGiven the function above all that remains to be done is to group the returned sequence into\ntuples of size 2 or 3 and convert them into floats given the precision defined in the header.\n\nThis is the pseudocode for the case of a 2d polyline:\n\n```commandline\nfunction decode_flexpolyline_2d\n    input: encoded_deltas  # (= encoded string without first two header bytes)\n           precision_2d       # integer with number of decimal digits\n    output: array of (lat, lon) coordinate tuples\n\n    deltas := decode_signed_deltas(encoded_coordinates)\n\n    coordinates := array()\n    lat := 0\n    lon := 0\n    for i in [0, 1, ... length(deltas) / 2]\n        lat := lat + deltas[2 * i]\n        lon := lon + deltas[2 * i + 1]\n        coordinates.append(Tuple(lat / 10 ** precision_2d, lon / 10 ** precision_2d))\n    end_for\n\n    return coordinates\n\nend_function\n```\n\n## My favorite language is not supported. What now?\n\nFeel free to contribute an implementation. You can either use C-bindings, or\nprovide an implementation in your language. Take a look at the implementations in other languages\nalready available. Usually, the encoding/decoding is very straight-forward. The interface should\nmatch roughly the following API:\n\n```\nencode(coordinates, precision, third_dimension, third_dimension_precision) -\u003e string;\ndecode(string) -\u003e coordinates;\nget_third_dimension(string) -\u003e third_dimension;\n```\n\nTo test your implementation, use the polylines defined in [test/original.txt]. Depending on the \nround function available in the language the expected encoded and decoded files need to be used:\n\n* Round to nearest, ties away from zero:\n  * [test/round\\_half_up/encoded.txt]\n  * [test/round\\_half_up/decoded.txt]\n* Round to nearest, ties to even:\n  * [test/round\\_half_even/encoded.txt]\n  * [test/round\\_half_even/decoded.txt]\n\nCheck that encoded the original data results in the encoded form, and that decoding it again results \nin the decoded form.\nFormat of the unencoded data is:\n\n* 2d: `{(precision2d); [(lat, lon), ..., (lat, lon), ]}`\n* 3d: `{(precision2d, precision3d, type3d); [(lat, lon, z), ..., (lat, lon, z), ]}`\n\nFloating point numbers are printed with 15 digits decimal precision. Be aware that encoding is \nlossy: Decoding an encoded polyline will not always yield the original, and neither will encoding a \ndecoded polyline result in the same encoded representation.\n\n### Implementation hints:\n\n* 32-bit floats and integers are not sufficient for encodings with high precision,\nuse 64-bits instead\n\n## TODO\n\n* Extend provided tests. We should cover all combinations of parameters.\n* Add C-bindings for people who just want to wrap them.\n\n[Encoded Polyline Algorithm Format]: https://developers.google.com/maps/documentation/utilities/polylinealgorithm\n[test/original.txt]: test/original.txt\n[test/round\\_half_up/encoded.txt]: test/round_half_up/encoded.txt\n[test/round\\_half_up/decoded.txt]: test/round_half_up/decoded.txt\n[test/round\\_half_even/encoded.txt]: test/round_half_even/encoded.txt\n[test/round\\_half_even/decoded.txt]: test/round_half_even/decoded.txt\n\n## License\n\nCopyright (C) 2019 HERE Europe B.V.\n\nSee the [LICENSE](./LICENSE) file in the root of this project for license details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheremaps%2Fflexible-polyline","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fheremaps%2Fflexible-polyline","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheremaps%2Fflexible-polyline/lists"}