-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathloader.js
More file actions
178 lines (153 loc) · 8.14 KB
/
loader.js
File metadata and controls
178 lines (153 loc) · 8.14 KB
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
const LOADER = {
serverUrl: "https://raw.githubusercontent.com/Welko/rtvis-webgpu-tutorial/main",
/**
* @param {string} url
* @returns {Promise<Response>}
*/
load: async (url) => {
const response = await fetch(url);
// TODO: Keep track of loading progress
return response;
},
/**
* @param {string} url
* @returns {Promise<string>}
*/
loadString: async (url) => {
return (await LOADER.load(url)).text();
},
/**
* @param {string} url
* @returns {Promise<any>}
*/
loadJson: async (url) => {
return JSON.parse(await LOADER.loadString(url));
},
/**
* @param {string} url
* @returns {Promise<HTMLImageElement>}
*/
loadImage: async (url) => {
return new Promise(resolve => {
const image = new Image();
image.crossOrigin = "anonymous";
image.addEventListener("load", () => resolve(image));
image.src = url;
});
},
/**
* List of the columns in the CSV files, in order of how they appear in the CSV
* Source: https://www.data.gv.at/katalog/dataset/c91a4635-8b7d-43fe-9b27-d95dec8392a7
*
*/
baumkatogdCsvColumnList: /** @type {const} */([
"FID", // Apparently the string "BAUMKATOGD.<objectId>"
"OBJECTID", // Not sure what this means
"SHAPE", // Geographical coordaintes in the format "POINT (<longitude> <latitude>)"
"BAUM_ID", // The ID of the tree
"DATENFUEHRUNG", // Data management (e.g. "magistrat")
"BEZIRK", // DE: Wiener Gemeindebezirk, in dem der Baum steht | EN: Vienna district in which the tree stands
"OBJEKT_STRASSE", // DE: Anlage, Objekt oder Straße, in der der Baum steht | EN: Facility, object or street in which the tree stands
"GEBIETSGRUPPE", // DE: Gebiet, in dem der Baum steht (e.g. "MA 28 - Straße, Grünanlage") | EN: Area in which the tree stands (e.g. "MA 28 - Straße, Grünanlage")
"GATTUNG_ART", // DE: Information, um welche Baumart es sich handelt | EN: Information about the type of tree
"PFLANZJAHR", // DE: Das Jahr, in dem der Baum gepflanzt wurde | EN: The year the tree was planted
"PFLANZJAHR_TXT", // DE: Werte aus PFLANZJAHR in Textformat, Nullwerte werden in Text "nicht bekannt" | EN: Values from PFLANZJAHR in text format, null values are converted to text "nicht bekannt"
"STAMMUMFANG", // DE: Umfang des Baumstammes in cm in einem Meter Höhe | EN: Circumference of the tree trunk in cm at one meter height
"STAMMUMFANG_TXT", // DE: Werte aus STAMMUMFANG in Textformat mit Zusatz " cm", Nullwerte werden in Text "nicht bekannt" umgewandelt | EN: Values from STAMMUMFANG in text format with additional " cm", null values are converted to text "nicht bekannt"
"BAUMHOEHE", // DE: Einteilung in 8 Größenkategorien der Baumhöhe + Nullwert | EN: Division into 8 size categories of tree height + null value
"BAUMHOEHE_TXT", // DE: Größenkategorien der Baumhöhe in Textformat direkt aus Quelldatenbank, Leereinträge werden in Text "nicht bekannt" umgewandelt | EN: Size categories of tree height in text format directly from source database, empty entries are converted to text "nicht bekannt"
"KRONENDURCHMESSER", // DE: Einteilung in 8 Größenkategorien des Baumkronendurchmessers + Nullwert | EN: Division into 8 size categories of tree crown diameter + null value
"KRONENDURCHMESSER_TXT", // DE: Größenkategorien der Kronendurchmesser in Textformat direkt aus Quelldatenbank, Leereinträge werden in Text "nicht bekannt" umgewandelt | EN: Size categories of crown diameters in text format directly from source database, empty entries are converted to text "nicht bekannt"
"BAUMNUMMER", // DE: Nummer des Baumes | EN: Number of the tree
"SE_ANNO_CAD_DATA", // Not sure what this means. Often empty
]),
/**
* @type {Record<string, number>}
*/
baumkatogdCsvColumnEnum: {
// Automatically generated based on the order of the elements in column list (see the end of this file)
},
/**
* @param {boolean} lots True if we should load LOTS OF TREES (219,378 of them)
* False (default) loads only 100 trees
* @returns {Promise<TreeStore>}
*/
loadTrees: async (lots=false) => {
const file = `/data/BAUMKATOGD-${lots?219378:100}.csv`;
const csv = await LOADER.loadString(LOADER.serverUrl + file);
const csvLines = csv.split("\n");
// Verify if all columns are present
csvLines[0]
.split(",")
.filter((csvColumnName, i) => csvColumnName !== LOADER.baumkatogdCsvColumnList[i])
.forEach((csvColumnName, i) => {
console.error(`Column ${i} is "${csvColumnName}" but should be "${LOADER.baumkatogdCsvColumnList[i]}"`);
});
// The 1st line has the column names and the last line is empty
const numTrees = csvLines.length - 2;
// Initialize the data structure
const treeStore = new TreeStore(numTrees);
// Get buffers
const buffers = {
latLon: treeStore.getCoordinatesLatLonBuffer(),
info: treeStore.getInfoBuffer(),
};
// Parse the CSV lines
for (let i = 0; i < numTrees; ++i) {
// Split the line by comma, but ignore commas inside double quotes
// Source: https://stackoverflow.com/questions/11456850/split-a-string-by-commas-but-ignore-commas-within-double-quotes-using-javascript
const csvValues = csvLines[i + 1].match(/(?:[^",]+|"[^"]*")+|^(?=,)|(?<=,)/g);
if (csvValues === null) {
console.error(`Error parsing CSV line ${i + 1}: ${csvLines[i + 1]}`);
continue;
}
// Extract latitude and longitude
const lonLat = csvValues[LOADER.baumkatogdCsvColumnEnum.SHAPE];
const latLon = lonLat
.substring(7, lonLat.length - 1) // Remove the prefix "POINT (" and the suffix ")"
.split(" ") // Split into LONGITUDE then LATITUDE
.reverse() // Reverse the order to LATITUDE then LONGITUDE
.map((value) => parseFloat(value));
// Extract other buffer values
const treeHeightCategory = parseInt(csvValues[LOADER.baumkatogdCsvColumnEnum.BAUMHOEHE]);
const crownDiameterCategory = parseInt(csvValues[LOADER.baumkatogdCsvColumnEnum.KRONENDURCHMESSER]);
const district = parseInt(csvValues[LOADER.baumkatogdCsvColumnEnum.BEZIRK]);
const circumference = parseInt(csvValues[LOADER.baumkatogdCsvColumnEnum.STAMMUMFANG]);
// Store the values in the buffers
buffers.latLon[i * 2] = latLon[0];
buffers.latLon[i * 2 + 1] = latLon[1];
buffers.info[i * 4] = treeHeightCategory;
buffers.info[i * 4 + 1] = crownDiameterCategory;
buffers.info[i * 4 + 2] = district;
buffers.info[i * 4 + 3] = circumference;
// Store the remaining values in the more general data structure
treeStore.getOtherData()[i] = {
locationId: parseInt(csvValues[LOADER.baumkatogdCsvColumnEnum.OBJECTID]),
location: csvValues[LOADER.baumkatogdCsvColumnEnum.OBJEKT_STRASSE],
treeId: parseInt(csvValues[LOADER.baumkatogdCsvColumnEnum.BAUM_ID]),
dataManagement: csvValues[LOADER.baumkatogdCsvColumnEnum.DATENFUEHRUNG],
areaGroup: csvValues[LOADER.baumkatogdCsvColumnEnum.GEBIETSGRUPPE],
treeType: csvValues[LOADER.baumkatogdCsvColumnEnum.GATTUNG_ART],
yearPlanted: parseInt(csvValues[LOADER.baumkatogdCsvColumnEnum.PFLANZJAHR]),
treeNumber: parseInt(csvValues[LOADER.baumkatogdCsvColumnEnum.BAUMNUMMER]),
};
}
return treeStore;
},
loadMap: async() => {
const map = await LOADER.loadJson(LOADER.serverUrl + "/map/vienna.json");
await Promise.all(Object.entries(map.images).map(async ([imageKey, imageFile]) => {
const image = await LOADER.loadImage(LOADER.serverUrl + "/map/" + imageFile);
map.images[imageKey] = await createImageBitmap(image);
}));
for (const image of Object.values(map.images)) {
if (image.width !== image.height) {
// Why? I forgot. TODO: Find out why
throw new Error(`Image ${image.src} must be square but is ${image.width}x${image.height}`);
}
}
return map;
},
};
// Automatically generate the column enum based on the order of the elements in the column list
LOADER.baumkatogdCsvColumnList.forEach((columnName, i) => LOADER.baumkatogdCsvColumnEnum[columnName] = i);