|
@@ -1,8 +1,12 @@
|
1
|
|
-import { lat2tile, lon2tile, getTileSize } from './osm';
|
|
1
|
+import { lat2tile, lon2tile, getTileSize, lat2tileExact, lon2tileExact } from './osm';
|
2
|
2
|
import { TileServer, Page } from './types';
|
3
|
|
-import { createPdf } from './pdf';
|
|
3
|
+import { createPdf, PDFOptions } from './pdf';
|
4
|
4
|
import { downloadPages } from './download';
|
5
|
|
-import { clearTmp } from './utils';
|
|
5
|
+import { clearTmp, getFilename } from './utils';
|
|
6
|
+import { TILE_SIZE } from './route';
|
|
7
|
+import { MapConfig } from './config';
|
|
8
|
+import gm from 'gm';
|
|
9
|
+import log from './log';
|
6
|
10
|
|
7
|
11
|
// main executable function
|
8
|
12
|
export default async function map({
|
|
@@ -32,13 +36,72 @@ export default async function map({
|
32
|
36
|
const pages: Page[] = boundaries2pages({ north, west, south, east, pageSizeX, pageSizeY, zoom });
|
33
|
37
|
// download pages
|
34
|
38
|
await downloadPages(pages, tmp, tileServer);
|
|
39
|
+ // draw boundary
|
|
40
|
+ await drawBoundary(pages, { north, west, south, east, zoom, tmp, pageSizeX, pageSizeY });
|
35
|
41
|
// create pdf
|
36
|
|
- await createPdf(output, tmp);
|
|
42
|
+ await createPdf(output, tmp, {
|
|
43
|
+ pageSizeX,
|
|
44
|
+ pageSizeY,
|
|
45
|
+ links: boundaries2links({ north, west, south, east, pageSizeX, pageSizeY, zoom }),
|
|
46
|
+ });
|
37
|
47
|
// clean up downloaded pages
|
38
|
48
|
await clearTmp(tmp);
|
39
|
49
|
}
|
40
|
50
|
|
41
|
|
-function boundaries2pages({
|
|
51
|
+const drawBoundary = async (
|
|
52
|
+ pages: Page[],
|
|
53
|
+ {
|
|
54
|
+ north,
|
|
55
|
+ west,
|
|
56
|
+ south,
|
|
57
|
+ east,
|
|
58
|
+ zoom,
|
|
59
|
+ tmp,
|
|
60
|
+ pageSizeX,
|
|
61
|
+ pageSizeY,
|
|
62
|
+ }: Pick<MapConfig, 'north' | 'west' | 'south' | 'east' | 'zoom' | 'tmp' | 'pageSizeX' | 'pageSizeY'>,
|
|
63
|
+): Promise<void> => {
|
|
64
|
+ const tileBoundary = {
|
|
65
|
+ north: lat2tileExact(north, zoom),
|
|
66
|
+ south: lat2tileExact(south, zoom),
|
|
67
|
+ east: lon2tileExact(east, zoom),
|
|
68
|
+ west: lon2tileExact(west, zoom),
|
|
69
|
+ };
|
|
70
|
+
|
|
71
|
+ for (let i = 0, len = pages.length; i < len; ++i) {
|
|
72
|
+ log(`drawing boundary ${i + 1}/${len}`);
|
|
73
|
+ const page = pages[i];
|
|
74
|
+ const filename = getFilename(tmp, i);
|
|
75
|
+ const control = gm(filename);
|
|
76
|
+
|
|
77
|
+ const pixelBoundary = {
|
|
78
|
+ north: (tileBoundary.north - page.y) * TILE_SIZE,
|
|
79
|
+ south: (tileBoundary.south - page.y) * TILE_SIZE,
|
|
80
|
+ east: (tileBoundary.east - page.x) * TILE_SIZE,
|
|
81
|
+ west: (tileBoundary.west - page.x) * TILE_SIZE,
|
|
82
|
+ };
|
|
83
|
+
|
|
84
|
+ // draw boundary
|
|
85
|
+ const maxWidth = Math.max(pageSizeX, pageSizeY) * TILE_SIZE;
|
|
86
|
+ control.stroke('#000a', maxWidth).fill('#000f');
|
|
87
|
+ control.drawRectangle(
|
|
88
|
+ pixelBoundary.west - maxWidth / 2,
|
|
89
|
+ pixelBoundary.north - maxWidth / 2,
|
|
90
|
+ pixelBoundary.east + maxWidth / 2,
|
|
91
|
+ pixelBoundary.south + maxWidth / 2,
|
|
92
|
+ );
|
|
93
|
+
|
|
94
|
+ await new Promise<void>((resolve, reject) => {
|
|
95
|
+ // draw it
|
|
96
|
+ control.write(filename, err => {
|
|
97
|
+ if (err) return reject(err);
|
|
98
|
+ else return resolve();
|
|
99
|
+ });
|
|
100
|
+ });
|
|
101
|
+ }
|
|
102
|
+};
|
|
103
|
+
|
|
104
|
+const boundaries2links = ({
|
42
|
105
|
north,
|
43
|
106
|
west,
|
44
|
107
|
south,
|
|
@@ -54,21 +117,95 @@ function boundaries2pages({
|
54
|
117
|
pageSizeX: number;
|
55
|
118
|
pageSizeY: number;
|
56
|
119
|
zoom: number;
|
57
|
|
-}) {
|
58
|
|
- const x = lon2tile(west, zoom);
|
59
|
|
- const y = lat2tile(north, zoom);
|
|
120
|
+}): PDFOptions['links'] => {
|
|
121
|
+ const { width, height } = boundaries2size({ north, west, south, east, pageSizeX, pageSizeY, zoom });
|
60
|
122
|
|
|
123
|
+ const links: PDFOptions['links'] = [];
|
|
124
|
+ const pageSize = { width: pageSizeX * TILE_SIZE, height: pageSizeY * TILE_SIZE };
|
|
125
|
+ const breakPoints = {
|
|
126
|
+ x: [0, 0.25 * pageSize.width, 0.75 * pageSize.width, pageSize.width],
|
|
127
|
+ y: [0, 0.25 * pageSize.height, 0.75 * pageSize.height, pageSize.height],
|
|
128
|
+ };
|
|
129
|
+ for (let i = 0, len = width * height; i < len; ++i) {
|
|
130
|
+ const linksForPage: PDFOptions['links'][number] = [];
|
|
131
|
+ for (const yi of [-1, 0, 1]) {
|
|
132
|
+ for (const xi of [-1, 0, 1]) {
|
|
133
|
+ const url = i + yi * width + xi;
|
|
134
|
+ const x = breakPoints.x[xi + 1];
|
|
135
|
+ const y = breakPoints.y[yi + 1];
|
|
136
|
+ const w = breakPoints.x[xi + 2] - breakPoints.x[xi + 1];
|
|
137
|
+ const h = breakPoints.y[yi + 2] - breakPoints.y[yi + 1];
|
|
138
|
+ if (
|
|
139
|
+ url >= 0 &&
|
|
140
|
+ url < len &&
|
|
141
|
+ // we don't want to link to page itself
|
|
142
|
+ url !== i &&
|
|
143
|
+ // we don't want to go beyond left and right edges
|
|
144
|
+ url % width === (i % width) + xi &&
|
|
145
|
+ // we don't want to go beyond top and bottom edges
|
|
146
|
+ Math.floor(url / width) % height === (Math.floor(i / width) % height) + yi
|
|
147
|
+ ) {
|
|
148
|
+ linksForPage.push({ x, y, width: w, height: h, url });
|
|
149
|
+ }
|
|
150
|
+ }
|
|
151
|
+ }
|
|
152
|
+ links.push(linksForPage);
|
|
153
|
+ }
|
|
154
|
+ return links;
|
|
155
|
+};
|
|
156
|
+
|
|
157
|
+const boundaries2size = ({
|
|
158
|
+ north,
|
|
159
|
+ west,
|
|
160
|
+ south,
|
|
161
|
+ east,
|
|
162
|
+ pageSizeX,
|
|
163
|
+ pageSizeY,
|
|
164
|
+ zoom,
|
|
165
|
+}: {
|
|
166
|
+ north: number;
|
|
167
|
+ west: number;
|
|
168
|
+ south: number;
|
|
169
|
+ east: number;
|
|
170
|
+ pageSizeX: number;
|
|
171
|
+ pageSizeY: number;
|
|
172
|
+ zoom: number;
|
|
173
|
+}): { width: number; height: number } => {
|
61
|
174
|
const { width, height } = getTileSize({ north, west, south, east, zoom });
|
|
175
|
+ return {
|
|
176
|
+ width: Math.ceil(width / pageSizeX),
|
|
177
|
+ height: Math.ceil(height / pageSizeY),
|
|
178
|
+ };
|
|
179
|
+};
|
62
|
180
|
|
63
|
|
- const pagesX = Math.ceil(width / pageSizeX);
|
64
|
|
- const pagesY = Math.ceil(height / pageSizeY);
|
|
181
|
+function boundaries2pages({
|
|
182
|
+ north,
|
|
183
|
+ west,
|
|
184
|
+ south,
|
|
185
|
+ east,
|
|
186
|
+ pageSizeX,
|
|
187
|
+ pageSizeY,
|
|
188
|
+ zoom,
|
|
189
|
+}: {
|
|
190
|
+ north: number;
|
|
191
|
+ west: number;
|
|
192
|
+ south: number;
|
|
193
|
+ east: number;
|
|
194
|
+ pageSizeX: number;
|
|
195
|
+ pageSizeY: number;
|
|
196
|
+ zoom: number;
|
|
197
|
+}) {
|
|
198
|
+ const { width, height } = boundaries2size({ north, west, south, east, zoom, pageSizeX, pageSizeY });
|
65
|
199
|
|
66
|
|
- console.log('size', pagesX, 'x', pagesY, 'pages'); // tslint:disable-line:no-console
|
|
200
|
+ console.log('size', width, 'x', height, 'pages'); // tslint:disable-line:no-console
|
67
|
201
|
|
68
|
202
|
const pages: Page[] = [];
|
69
|
203
|
|
70
|
|
- for (let py = 0; py < pagesY; py++) {
|
71
|
|
- for (let px = 0; px < pagesX; px++) {
|
|
204
|
+ const x = lon2tile(west, zoom);
|
|
205
|
+ const y = lat2tile(north, zoom);
|
|
206
|
+
|
|
207
|
+ for (let py = 0; py < height; py++) {
|
|
208
|
+ for (let px = 0; px < width; px++) {
|
72
|
209
|
pages.push({
|
73
|
210
|
x: x + px * pageSizeX,
|
74
|
211
|
y: y + py * pageSizeY,
|