Skip to content
This repository was archived by the owner on Feb 22, 2025. It is now read-only.

Commit 4a1c54e

Browse files
committed
feat(claims): ✨ Building counts
1 parent 6378de0 commit 4a1c54e

File tree

8 files changed

+115
-2
lines changed

8 files changed

+115
-2
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"@prisma/client": "^4.7.1",
5050
"@turf/helpers": "^6.5.0",
5151
"@turf/turf": "^6.5.0",
52+
"axios": "^1.6.7",
5253
"blurhash": "^2.0.5",
5354
"body-parser": "^1.20.1",
5455
"cors": "^2.8.5",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- AlterTable
2+
ALTER TABLE "Claim" ADD COLUMN "buildings" INTEGER NOT NULL DEFAULT 1,
3+
ADD COLUMN "city" TEXT,
4+
ADD COLUMN "osmName" TEXT;

prisma/schema.prisma

+3
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ model Claim {
142142
finished Boolean @default(false)
143143
name String @default("")
144144
description String?
145+
osmName String?
146+
city String?
147+
buildings Int @default(0)
145148
Application Application[]
146149
owner User? @relation("owner", fields: [ownerId], references: [id])
147150
builders User[] @relation("builders")

src/Core.ts

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import Keycloak from "keycloak-connect";
1212
import AmazonAWS from "./util/AmazonAWS.js";
1313
import CronHandler from "./util/CronHandler.js";
1414
import DiscordIntegration from "./util/DiscordIntegration.js";
15-
import { rerenderFrontend } from "./util/Frontend.js";
1615
import KeycloakAdmin from "./util/KeycloakAdmin.js";
1716
import Web from "./web/Web.js";
1817

src/controllers/AdminController.ts

+69-1
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@ import { Request, Response } from "express";
22

33
import Core from "../Core.js";
44
import { ERROR_GENERIC } from "../util/Errors.js";
5+
import axios from "axios";
6+
import { toOverpassPolygon } from "../util/Coordinates.js";
57

68
class AdminController {
79
private core: Core;
10+
private progress;
811

912
constructor(core: Core) {
1013
this.core = core;
14+
this.progress = {
15+
buildings: { done: 0, total: 0 },
16+
adresses: { done: 0, total: 0 },
17+
};
1118
}
1219

13-
// CRON
1420
public getCronJobs(req: Request, res: Response) {
1521
const jobs = this.core.getCron().getAll();
1622

@@ -27,6 +33,68 @@ class AdminController {
2733
})
2834
);
2935
}
36+
37+
public getProgress(req: Request, res: Response) {
38+
res.send(this.progress);
39+
}
40+
41+
public async getClaimBuildingCounts(req: Request, res: Response) {
42+
if (this.progress.buildings > 0) {
43+
return ERROR_GENERIC(res, 409, "Recalculations are already ongoing.");
44+
}
45+
46+
const claims = await this.core.getPrisma().claim.findMany({
47+
where: {
48+
center: { not: null },
49+
buildings:
50+
req.query.skipExisting === "true"
51+
? 1
52+
: {
53+
gte: req.query.take ? parseInt(req.query.gte as string) : 0,
54+
},
55+
},
56+
take: req.query.take && parseInt(req.query.take as string),
57+
skip: req.query.skip ? parseInt(req.query.skip as string) : 0,
58+
select: { buildings: true, id: true, area: true },
59+
});
60+
61+
res.send({ progress: 0, count: claims.length });
62+
this.progress.buildings.total = claims.length;
63+
64+
for (const [i, claim] of claims.entries()) {
65+
const polygon = toOverpassPolygon(claim.area);
66+
67+
const overpassQuery = `[out:json][timeout:25];
68+
(
69+
node["building"]["building"!~"grandstand"]["building"!~"roof"]["building"!~"garage"]["building"!~"hut"]["building"!~"shed"](poly: "${polygon}");
70+
way["building"]["building"!~"grandstand"]["building"!~"roof"]["building"!~"garage"]["building"!~"hut"]["building"!~"shed"](poly: "${polygon}");
71+
relation["building"]["building"!~"grandstand"]["building"!~"roof"]["building"!~"garage"]["building"!~"hut"]["building"!~"shed"](poly: "${polygon}");
72+
);
73+
out count;`;
74+
75+
const { data } = await axios.get(
76+
`https://overpass.kumi.systems/api/interpreter?data=${overpassQuery.replace(
77+
"\n",
78+
""
79+
)}`
80+
);
81+
this.core
82+
.getLogger()
83+
.debug(
84+
"Getting buildings for claim " +
85+
claim.id +
86+
` (${i + 1}/${claims.length})`
87+
);
88+
89+
const updatedClaim = await this.core.getPrisma().claim.update({
90+
where: { id: claim.id },
91+
data: { buildings: parseInt(data?.elements[0]?.tags?.total) || 0 },
92+
});
93+
94+
this.progress.buildings.done = i + 1;
95+
}
96+
this.progress.buildings = { done: 0, total: 0 };
97+
}
3098
}
3199

32100
export default AdminController;

src/util/Coordinates.ts

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export function toLngLat(
3636
};
3737
}
3838

39+
export function toOverpassPolygon(coords: string[]): string {
40+
return coords.map((c) => `${c.split(", ")[1]} ${c.split(", ")[0]}`).join(" ");
41+
}
42+
3943
// Parses any acceptable coordinate input into an uniform type (["lng, lat","lng, lat"])
4044
export function useCoordinateInput(coordinates: string, required?: boolean) {
4145
return async (req: Request, res: Response, next: NextFunction) => {

src/web/routes/index.ts

+20
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,26 @@ class Routes {
824824
},
825825
checkUserPermission(this.web.getCore().getPrisma(), "admin.admin")
826826
);
827+
router.addRoute(
828+
RequestMethods.GET,
829+
"/admin/progress",
830+
async (request, response) => {
831+
await adminController.getProgress(request, response);
832+
},
833+
checkUserPermission(this.web.getCore().getPrisma(), "admin.admin")
834+
);
835+
router.addRoute(
836+
RequestMethods.POST,
837+
"/admin/claims/buildings",
838+
async (request, response) => {
839+
await adminController.getClaimBuildingCounts(request, response);
840+
},
841+
query("skipExisting").isBoolean().optional(),
842+
query("take").isNumeric().optional(),
843+
query("skip").isNumeric().optional(),
844+
query("gte").isNumeric().optional(),
845+
checkUserPermission(this.web.getCore().getPrisma(), "admin.admin")
846+
);
827847
}
828848
}
829849

yarn.lock

+14
Original file line numberDiff line numberDiff line change
@@ -2661,6 +2661,15 @@ axios@^1.6.0:
26612661
form-data "^4.0.0"
26622662
proxy-from-env "^1.1.0"
26632663

2664+
axios@^1.6.7:
2665+
version "1.6.7"
2666+
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7"
2667+
integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==
2668+
dependencies:
2669+
follow-redirects "^1.15.4"
2670+
form-data "^4.0.0"
2671+
proxy-from-env "^1.1.0"
2672+
26642673
b4a@^1.6.4:
26652674
version "1.6.4"
26662675
resolved "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz"
@@ -3655,6 +3664,11 @@ follow-redirects@^1.14.9, follow-redirects@^1.15.0:
36553664
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz"
36563665
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
36573666

3667+
follow-redirects@^1.15.4:
3668+
version "1.15.5"
3669+
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020"
3670+
integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==
3671+
36583672
form-data@^4.0.0:
36593673
version "4.0.0"
36603674
resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz"

0 commit comments

Comments
 (0)