1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
import * as Base from 'lib/base'
import * as Icons from 'lib/icons'
import * as State from 'state'
import * as Utils from 'serialization/utils'
const posPrecision: number = 5
const latLength: number = Base.encode(BigInt(`180${'0'.repeat(posPrecision)}`), Base.b2).length
const lngLength: number = Base.encode(BigInt(`360${'0'.repeat(posPrecision)}`), Base.b2).length
const colorLength: number = Base.encode(Base.decode('ffffff', Base.b16), Base.b2).length
const iconLength: number = 8 // At most 255 icons
const radiusLength: number = 5
// Encoding
export function encode(s: State.State): string[] {
return s.map(encodeMarker)
}
function encodeMarker({ pos, name, color, icon, radius }: State.Marker): string {
const lat = encodeLatOrLng(pos.lat, latLength, 180) // [-90; 90]
const lng = encodeLatOrLng(pos.lng, lngLength, 360) // [-180; 180]
return lat + lng + encodeColor(color) + encodeIcon(icon) + encodeRadius(radius) + encodeName(name)
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
export const uriComponentBase: string[] =
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.!~*\'()%'.split('')
function encodeLatOrLng(n: number, length: number, range: number): string {
const [a, b] = Utils.mod(n + range / 2, range).toFixed(posPrecision).split('.')
return Utils.encodeNumber(BigInt(a + b), length)
}
function encodeColor(color: string): string {
return Utils.encodeNumber(Base.decode(color.slice(1).toLowerCase(), Base.b16), colorLength)
}
function encodeIcon(icon: string): string {
return icon == ''
? '0'
: `1${Utils.encodeNumber(BigInt(Icons.keys().indexOf(icon) + 1), iconLength)}`
}
function encodeRadius(radius: number): string {
if (radius == 0) {
return '0'
} else {
const binary = Base.encode(BigInt(radius), Base.b2)
const binaryLength = Utils.encodeNumber(BigInt(binary.length), radiusLength)
return `1${binaryLength}${binary}`
}
}
function encodeName(str: string): string {
return str == ''
? ''
: Base.encode(Base.decode(encodeURIComponent(str), uriComponentBase), Base.b2)
}
// Decoding
export function decode(encoded: string[]): State.State {
return encoded.map(binary => {
const [ lat, i1 ] = decodeLatOrLng(binary, 0, latLength, 180)
const [ lng, i2 ] = decodeLatOrLng(binary, i1, lngLength, 360)
const [ color, i3 ] = decodeColor(binary, i2)
const [ icon, i4 ] = decodeIcon(binary, i3)
const [ radius, i5 ] = decodeRadius(binary, i4)
const name = decodeName(binary, i5)
return {
pos: { lat, lng },
name,
color,
icon,
radius,
}
})
}
function decodeLatOrLng(encoded: string, i: number, length: number, range: number): [number, number] {
const slice = encoded.slice(i, i + length)
const digits = Base.decode(slice, Base.b2).toString()
const latOrLng = parseFloat(`${digits.slice(0, -posPrecision)}.${digits.slice(-posPrecision)}`) - range / 2
return [ latOrLng, i + length ]
}
function decodeColor(encoded: string, i: number): [ string, number ] {
const slice = encoded.slice(i, i + colorLength)
const color = `#${Base.encode(Base.decode(slice, Base.b2), Base.b16)}`
return [ color, i + colorLength ]
}
function decodeIcon(encoded: string, i: number): [ string, number ] {
if (encoded.slice(i, i + 1) == '0') {
return [ '', i + 1 ]
} else {
const slice = encoded.slice(i + 1, i + 1 + iconLength)
const iconIndex = Number(Base.decode(slice, Base.b2)) - 1
const icon = iconIndex < 0 ? '' : Icons.keys()[iconIndex]
return [ icon, i + 1 + iconLength ]
}
}
function decodeRadius(encoded: string, i: number): [ number, number ] {
if (encoded.slice(i, i + 1) == '0') {
return [ 0, i + 1 ]
} else {
const binaryLength = encoded.slice(i + 1, i + 1 + radiusLength)
const length = Number(Base.decode(binaryLength, Base.b2))
const binary = encoded.slice(i + 1 + radiusLength, i + 1 + radiusLength + length)
const radius = Number(Base.decode(binary, Base.b2))
return [ radius, i + 1 + radiusLength + length ]
}
}
function decodeName(encoded: string, i: number): string {
const slice = encoded.slice(i)
return slice == ''
? ''
: decodeURIComponent(Base.encode(Base.decode(slice, Base.b2), uriComponentBase))
}
|