From fa516b3a20e4bc817782b7b8dcf8f1e22e77f301 Mon Sep 17 00:00:00 2001 From: popovspiridon99 Date: Mon, 22 Dec 2025 17:35:52 +0900 Subject: [PATCH] moving features: write to sqlite modified features --- client/src/components/map/mapUtils.ts | 284 +++++++++++++++++++- client/src/interfaces/gis.ts | 7 +- client/src/store/map.ts | 3 +- server/ems.db | Bin 13762560 -> 13799424 bytes server/src/gis/dto/update-features-batch.ts | 8 + server/src/gis/gis.controller.ts | 6 + server/src/gis/gis.service.ts | 169 +++++++++++- server/src/main.ts | 2 + 8 files changed, 452 insertions(+), 27 deletions(-) create mode 100644 server/src/gis/dto/update-features-batch.ts diff --git a/client/src/components/map/mapUtils.ts b/client/src/components/map/mapUtils.ts index 238d253..6680d55 100644 --- a/client/src/components/map/mapUtils.ts +++ b/client/src/components/map/mapUtils.ts @@ -22,6 +22,7 @@ import VectorImageLayer from "ol/layer/VectorImage"; import VectorSource from "ol/source/Vector"; import Map from "ol/Map"; import { Icon, Style } from "ol/style"; +import axiosInstance from "../../http/axiosInstanceNest"; export function getCitySettings() { return { @@ -94,11 +95,26 @@ export const addFigures = ( if (figuresData.length > 0) { const geoJsonObject = { type: "FeatureCollection", - features: figuresData.map((figure: IFigure) => processFigure( - figure, - settings.scale, - [center[0], center[1]] - )), + features: figuresData.map((figure: IFigure) => { + if (figure.modified) { + console.log("found modified", JSON.parse(figure.modified)) + return { + ...JSON.parse(figure.modified), properties: { + year: figure.year, + figure_type_id: figure.figure_type_id, + type: figure.type, + object_id: figure.object_id, + planning: figure.planning + } + } + } else { + return processFigure( + figure, + settings.scale, + [center[0], center[1]] + ) + } + }), } const features = new GeoJSON().readFeatures(geoJsonObject) @@ -127,7 +143,22 @@ export const addLines = ( if (linesData.length > 0) { const geoJsonObject = { type: "FeatureCollection", - features: linesData.map((line: ILine) => processLine(line, settings.scale, [center[0], center[1]])), + features: linesData.map((line: ILine) => { + if (line.modified) { + return { + ...JSON.parse(line.modified), properties: { + year: line.year, + type: line.type, + geometry_type: 'line', + object_id: line.object_id, + planning: line.planning, + } + } + } else { + return processLine(line, settings.scale, [center[0], center[1]]) + } + + }), } const features = new GeoJSON().readFeatures(geoJsonObject) @@ -144,6 +175,39 @@ export const calculateAngle = (coords: [number, number][]) => { return Math.atan2(dy, dx); // Angle in radians } +export function processLineFeatureToLine( + feature: Feature, + scaling: number, + mapCenter: Coordinate +): Partial { + const geometry = feature.getGeometry() as LineString; + + // Get the line coordinates + const coordinates = geometry.getCoordinates(); + + if (coordinates.length < 2) { + throw new Error('Line must have at least 2 points'); + } + + // Get start and end points + const startPoint = coordinates[0]; + const endPoint = coordinates[coordinates.length - 1]; + + // Convert from map coordinates to database coordinates + const x1 = (startPoint[0] - mapCenter[0]) / scaling; + const y1 = (mapCenter[1] - startPoint[1]) / scaling; + + const x2 = (endPoint[0] - mapCenter[0]) / scaling; + const y2 = (mapCenter[1] - endPoint[1]) / scaling; + + return { + x1: parseFloat(x1.toFixed(6)), + y1: parseFloat(y1.toFixed(6)), + x2: parseFloat(x2.toFixed(6)), + y2: parseFloat(y2.toFixed(6)), + }; +} + export function processLine( line: ILine, scaling: number, @@ -167,6 +231,7 @@ export function processLine( type: "Feature", geometry: new GeoJSON().writeGeometryObject(geometry), properties: { + year: line.year, type: line.type, geometry_type: 'line', object_id: line.object_id, @@ -176,6 +241,158 @@ export function processLine( } } +export function processFeatureToFigure( + feature: Feature, + figureTypeId: number, + scaling: number, + mapCenter: Coordinate, +): Partial { + const geometry = feature.getGeometry() + const properties = feature.getProperties(); + + switch (figureTypeId) { + case 1: // Ellipse + return processEllipseFeature(geometry as Polygon, scaling, mapCenter); + + case 3: // Custom polygon + return processPolygonFeature(geometry as Polygon, scaling, mapCenter); + + case 4: // Rotated rectangle + return processRectangleFeature(geometry as Polygon, scaling, mapCenter, properties.angle); + + default: + throw new Error(`Unsupported figure type: ${figureTypeId}`); + } +} + +function processEllipseFeature(geometry: Polygon, scaling: number, mapCenter: Coordinate): Partial { + // Get the extent of the ellipse + const extent = geometry.getExtent(); + + // Calculate dimensions + const width = (extent[2] - extent[0]) / scaling; + const height = (extent[3] - extent[1]) / scaling; + + // Calculate center in map coordinates + const centerX = (extent[0] + extent[2]) / 2; + const centerY = (extent[1] + extent[3]) / 2; + + // Convert to database coordinates + const left = (centerX - mapCenter[0]) / scaling; + const top = (mapCenter[1] - centerY) / scaling; + + return { + figure_type_id: 1, + width: parseFloat(width.toFixed(6)), + height: parseFloat(height.toFixed(6)), + left: parseFloat(left.toFixed(6)), + top: parseFloat(top.toFixed(6)) + }; +} + +function processPolygonFeature( + geometry: Polygon, + scaling: number, + mapCenter: Coordinate, +): Partial { + // Get polygon coordinates (first ring only) + const coordinates = geometry.getCoordinates()[0]; + + // Calculate the bounding box to get left/top + let minX = Infinity, maxX = -Infinity; + let minY = Infinity, maxY = -Infinity; + + coordinates.forEach(coord => { + minX = Math.min(minX, coord[0]); + maxX = Math.max(maxX, coord[0]); + minY = Math.min(minY, coord[1]); + maxY = Math.max(maxY, coord[1]); + }); + + // Calculate center of the polygon + const centerX = (minX + maxX) / 2; + const centerY = (minY + maxY) / 2; + + // Convert center to database coordinates for left/top + const left = (centerX - mapCenter[0]) / scaling; + const top = (mapCenter[1] - centerY) / scaling; + + // Now convert all points relative to this center + const pointsArray = coordinates.map(coord => { + // Points are relative to (left, top), which is our calculated center + const relativeX = (coord[0] - mapCenter[0]) / scaling - left; + const relativeY = (mapCenter[1] - coord[1]) / scaling - top; + return `${relativeX.toFixed(6)};${relativeY.toFixed(6)}`; + }); + + // Join points with space (skip last point if it's duplicate of first) + const points = pointsArray.slice(0, -1).join(' '); + + return { + figure_type_id: 3, + points, + left: parseFloat(left.toFixed(6)), + top: parseFloat(top.toFixed(6)) + }; +} + +function processRectangleFeature( + geometry: Polygon, + scaling: number, + mapCenter: Coordinate, + angle: number = 0 +): Partial { + // Clone geometry to avoid modifying original + const workingGeometry = geometry.clone(); + + // Get the current center + const currentCenter = getCenter(workingGeometry.getExtent()); + + // If there's rotation, we need to unrotate to get axis-aligned bounds + if (angle !== 0) { + workingGeometry.rotate(angle * Math.PI / 180, currentCenter); + } + + // Get the unrotated extent + const extent = workingGeometry.getExtent(); + + // Calculate dimensions from unrotated geometry + const width = (extent[2] - extent[0]) / scaling; + const height = (extent[3] - extent[1]) / scaling; + + // Calculate center of unrotated geometry + const unrotatedCenter = getCenter(extent); + + // Convert to database coordinates + const left = (unrotatedCenter[0] - mapCenter[0]) / scaling; + const top = (mapCenter[1] - unrotatedCenter[1]) / scaling; + + return { + figure_type_id: 4, + width: parseFloat(width.toFixed(6)), + height: parseFloat(height.toFixed(6)), + left: parseFloat(left.toFixed(6)), + top: parseFloat(top.toFixed(6)), + angle: angle // Preserve the angle + }; +} + +// Helper function to update a feature after movement +export function updateFeatureCoordinates( + feature: Feature, + scaling: number, + mapCenter: Coordinate +): void { + const figureTypeId = feature.get('figure_type_id'); + const updatedFigure = processFeatureToFigure(feature, figureTypeId, scaling, mapCenter); + + // You can now send updatedFigure to your backend + console.log('Updated figure data:', updatedFigure); + + // Example: Update feature properties with database coordinates for reference + feature.set('db_coordinates', updatedFigure); +} + export function processFigure( figure: IFigure, scaling: number, @@ -201,6 +418,8 @@ export function processFigure( type: "Feature", geometry: new GeoJSON().writeGeometryObject(ellipseGeom), properties: { + year: figure.year, + figure_type_id: figure.figure_type_id, type: figure.type, object_id: figure.object_id, planning: figure.planning @@ -229,6 +448,8 @@ export function processFigure( type: "Feature", geometry: new GeoJSON().writeGeometryObject(polygon), properties: { + year: figure.year, + figure_type_id: figure.figure_type_id, type: figure.type, object_id: figure.object_id, planning: figure.planning @@ -264,6 +485,8 @@ export function processFigure( type: "Feature", geometry: new GeoJSON().writeGeometryObject(geometry1), properties: { + year: figure.year, + figure_type_id: figure.figure_type_id, type: figure.type, object_id: figure.object_id, planning: figure.planning, @@ -590,9 +813,54 @@ export const addInteractions = ( } if (currentTool == 'Mover') { - setTranslate(map_id, new Translate({ + const translateMode = new Translate({ features: new Collection(getSelectedFeatures(map_id)) - })) + }) + + translateMode.on('translateend', async (e) => { + const features = e.features.getArray() + + let changesJSON: any = [] + + features.map((f: Feature) => { + if (f.get('geometry_type') === 'line') { + const json = new GeoJSON() + + changesJSON.push({ + object_id: f.get('object_id'), + year: f.get('year'), + type: 'line', + feature: JSON.parse(json.writeFeature(f, { + featureProjection: 'EPSG:3857', + dataProjection: 'EPSG:3857' + })) + }) + } else { + const json = new GeoJSON() + + changesJSON.push({ + object_id: f.get('object_id'), + year: f.get('year'), + type: 'figure', + feature: JSON.parse(json.writeFeature(f, { + featureProjection: 'EPSG:3857', + dataProjection: 'EPSG:3857' + })) + }) + } + }) + + console.log(changesJSON) + + await axiosInstance.post(`/gis/features/update`, { + features: changesJSON + }, + { + baseURL: import.meta.env.VITE_API_NEST_URL, + }) + }) + + setTranslate(map_id, translateMode) const translate = getTranslate(map_id) if (translate) { diff --git a/client/src/interfaces/gis.ts b/client/src/interfaces/gis.ts index fb5856a..ba9f8c8 100644 --- a/client/src/interfaces/gis.ts +++ b/client/src/interfaces/gis.ts @@ -4,7 +4,6 @@ import { SatelliteMapsProvider } from "./map"; import Map from "ol/Map"; import { Coordinate } from "ol/coordinate"; import { Mode } from "../store/map"; - export interface IRegion { id: number name: string @@ -30,7 +29,8 @@ export interface IFigure { label_size: number | null, year: number, type: number, - planning: boolean + planning: boolean, + modified?: string } export interface ILine { @@ -49,7 +49,8 @@ export interface ILine { label_positions: string | null, year: number, type: number, - planning: boolean + planning: boolean, + modified?: string } export interface ICitySettings { diff --git a/client/src/store/map.ts b/client/src/store/map.ts index 8fbf3b0..8d5595b 100644 --- a/client/src/store/map.ts +++ b/client/src/store/map.ts @@ -27,7 +27,6 @@ import { getSelectedRegion, setCurrentObjectId, setSelectedDistrict, setSelected import View from 'ol/View'; import { getPrintOrientation } from './print'; import { getDistrictsData, getRegionsData } from './regions'; -import { fromExtent } from 'ol/geom/Polygon'; export type Mode = 'edit' | 'view' | 'print' @@ -273,7 +272,7 @@ export const initializeMapState = ( condition: noModifierKeys }) - selectionDragBox.on('boxend', (e) => { + selectionDragBox.on('boxend', () => { const extent = selectionDragBox.getGeometry().getExtent() const figuresSource = figuresLayer.getSource() diff --git a/server/ems.db b/server/ems.db index b285df1f658cd32f3b900fdea41f906fc855a301..88ec319b847d2738bbfc0eba92654621f73d876f 100644 GIT binary patch delta 45485 zcmb@v2Y6IP`#!wq?4CWlo3f#Xo(+)RdqNE*KxhdiBm_u-B!INg1%zai-Nl9k5e9p# zSTU%{0c^1#A_zfHuo4vuiW<9s!gt>rJjnZd|KIidf7b^W_nDdJnK^Ujne)tjW@d-# z>bK0*^>3T&dImQe41N)YtAF|-Xu1;h!&MR;GNr!#jm?JGwUvJUjRqgvK!e@m|B_#k z|IL2BK2Q37;xo#2(RQ0{pw=Ov`%a7I$do!tRg-+ofiXR$`STZ-m0v%r`ro437m9XE4$yvjuW;wDaj?5}>{HKy5XQJbZVmLEJEU>A81A9tNzyDF@lG)dl z&sp*BqN{xWk7fT$`QNv=l03gG+Tp!hw$&UM5h#`FH(NM=@s;X(Lq7lG23HjvIaL!K z-e`Nx=UCLhv_Q$J{6}H`s)|?J_J1hzUwW)^d&&P<;hH$J8rZX^yrL==cCBpAvhvauaCD;y(X^{LqtvK%9`Mh(jw zKh-fJZ}Mc^q?n0QrVg8s+g{wt%Ci59ERMbvxC1&_U&qv{tb%{nD{o@S9d&4I&uN(QfCmM>j6e?@t5>B<#Lym7tHCR}MmYp!ArjEa(`qlFiiUr}FPzI0{z z;yKqwtLBue*R=S5D2FdDT~r>8tPwR4Ml~=nP;w=B3-#L83(7V5y-ELjdsY448tngP zdsY2!Z2WJ7{V(kmVKN5}4*Wma%iBoB3Am``%gyj!mgn~RPm=mcbZGzYaC<=6*4zNa zW`w503tJO9L`#udhXsT!Nu)kYk{MwQ_#Z-g$*x>h=d zcaE5(i}S7(PrEic9X0Iit#&C(E#5k;OT4dPfu4H-<=uJLO2YDMbFbLCv}2w^!Ho#r zy4BIiU)t$?@&@GV5Tn!z#s-jB|^7h_hT5yAL-q5F8gF7aAL*F57 zls9x?Yllv~eJ=fzPclGfq%8Tf7JNFX3!YBuiqy0&jQ0P}du@HCOJG&x)l@1aGfylb zju1~sAS7PZ)9;w7htl=X04fh83}SiS=nzJS5;6(H2*bTnV-~J6SMWQawr%} z7)KaSn4n9iGMY=6M3}4#W-^*bm`2Da6cDBp3UxhNze<|L#3DkmE-7PlwjQmx8lqq> zYgxh2e1;Y=r1zfo=we+^N$FC;GQx5Su4iZ^VO5o0^m@kE5Y`gbT`SnYv?@Y1OK>w( zL#WmDZf4X&*hsih7u?3^EreV3v^yBxY%)|Cq&fz-5N_AS_b|GRu$>?XI|z3Y?$Y(@ z8NHWqpPu#zqYn@sBs_Gj;8CXS)YGbVG5!SMNy1aQ#4C7)X$^$kglGBedl`D3&`4+^ zXnHoUbRW}RAiPL;iSRP>?q{f(@G9Xo6Z@b3I!Xtb_$HzK#blOu8GV~@h;W$j4&eyl zD4|8a`zc0`6HXA`BfRgG>MEZy?L)#xfGX+ZYbB>CY2}SSC7jWvUsLd<9zATy2p~tm?-c0L52;x1x%E65G zWi*nZ{)7-hC?Skc6|NT(sVhb^#1Tx6CBzZpb-iS7NRR46o18|ybmkqz&;VV9o=_gf zv>}A{p^a%H6g+=>1B{|%q@E?4(a~3<)Ske!aeA6JI+21Y6wnjO`Vc77^~qCp<$|jr z3Z@eZ0afx0mN19$S%e}&F`v;tWnYM^}^m_6VLZz-7@6QF=nzMU@&|;ElSO<{{7%$~O{jqVgt& zZqbzytun~BF>y1+baOI2p?o`GD}jzu-lk`{^J<8Ky9jp^?qLCRhqCSo<;NJMCzR<4 z<%b9l6CNQv%A%@BkL1S)PciWcy?k%r1#eONFhg(aMSG*~P@wxixrHjn2=5Y( zQ~Du8^n~*Jgp-6*|CF+IV4ogUXPDGV_>A!RKSKFyMs=Sj>poBZiGptl-x1Ccz9;-Z z_)#ywtM@b0eqmMGeI7%9Gwlz;pBw|^^9)`fT+}mrbuK~MDE*UWG(*KG6O07YwE~sO zbaFd`b1E_p_C?45KV|7(5V^e)Qq}QYgb8PTCxEpNasvXqj#F`e$LnNu&HXh z2W@1&)w2Ouv(sMnq(z6;k*Ru+k?^yvH?O6uQ^i%>9JxhT7Jd1#x zJqv(V&pe>fQ(-VDmptXbKRji?Up;ex-+5*Ozx0#0;&2|VJN z0es6-2zNeA}wqyYmxsX$*(3Q+MR0gavn%n1GN zi3k4Ri3NV+i3XnWL;^qcVDiZHp(hM@!V?NScC*(GvuG!P5uW;OULV zH$CC$3Eb)F4!qyf4S1KQD^Pek1GjlP0dMzo1a9(l0N&&Y06IPXz;zy9;3|&|xZGm_ zF7>Fu#U6a|nHG9ra8o(`rK!Y&Z$49zn|GYyhPh4o?)7kzrtxlA-89Axdz(hNVQV~~dY3`N4IQMd3qClI?Bj;*O+DS$0lT~B0|VXY z2c|CWxxfH7dV|T=jecNK+@ z?hs(28-2nUUgbuoF!pn!I~cpWdjq?<(Ho3i+&zGu+~^ZVA2<4h(dLiz3%xT-dr95~H|H(s9Nnhl)bg4N}07tAe>a>3B@2-i&Ba+nMD zmIt|DZ#m5cOUo%PSXxeY!P0V~3zn86T~mO)T(Gnp=z?|S&MsJ2?&ul^3~<55vcC&9 zmMt#WSXNwFaCMhmFs^jL1+z+jx?oD_ch?}`uPzu=`q?#rA4zSl3`EYk(t&4Pu&ea7 zD;apk1-nY0x)OmOxL{Z5lnZv1-gUvQ(m@yOD(!bg0r$FKU1^sK)|DQ2g#sUOg#hba z{ebtlU}@m(pFZv6SjE15?PAq+}P2E5*2ATq(L1W|bmq z*8(GIVM-~y7N(S{LTf8=38`HS>|cvFSn68~`$@sIbAgW9GGI_G>?if9h5e-7wI#q_ zwXmYpv$hD>qZYQ5y4S*pQn%Xaz%I2gqSUz-MwB|$!iZAGS{PC4P&*kIPzxhU{fwwv50yjC?)VDg> zytg>nyf-`9us1pLfj2tYupTGd((Pnhx}4*IwN7+!LyePd>2$I!tDS7isw!s|E*qR| z()CXCaKjBwHtaeldbnY&lMTDZ37=)S-pS@&?PT+=a;5=SIN8saJK4yUPB!ckC);wd z6Hdvn$jMe*;Oq~a?_^ufa|Q$FIvv0YXCGj>vx*H{=48XparOYtcCvX(on3)NPWJ6t z&W^yDPWJ5?PB!v%C;N7R(*~U8v;y;-DsYOEO+Cpe19P1KLk|1*#Pxs)Z1eH#*5lZp z$6gN@vzq;H^a=#CmI8*e;fJx^hqBp+uxk%yqYq@84`9E}C zpCsJ(2uZkaGfBAbZjx}{2T8(x1xdK?7Lst^TS>xw-6Y|@H6-D_Ye>R_U?wdvu?wdps?i)=K?%S6n+}D>R z+*c(D_mxP(eSRYe_h};u_c=!r?(;QCxXxwwvdGTxJbf%ZXgNwSw#}=vy3F%XCX#pc7Y_^_6te4?FW)@+c}bO+gXxu+m~JvZu^iV z-1a_6xa|Z_;kHJSaNBbv;kMl*;kI2Q;kKtp!flU} zgxhwKgxek=3Aa5&5^lSXB;0lvNw{q*Nw{s38$F=Pb_+?k?Ix0NTP;brt%@YvwvHs+ zwwff|wu~g)wuB_ywumI$R!$Obn@tjKDrRqz>%%1B)(1(#t#^`yTkjwVw{9j0x86b$Zgr7_TdPRIt*c4Gtt&~wt;oF@sl{6rFN`HCdma)u<_@)=3E=`!YxT8;g(pE za7!dfxTP;ixTTlN0qjN+Zt3JD;T9i~aEqBFT)jvVuKr3AuKr9CuKq|8u6|1ru6|Au zu6{xiuAU?bSKlKESKlHDSKlNFSKlBBS6?RyS6?FuS6^_!xavNVaCHw!xT=wat4$=~ zYSr^3;p%fF;p(#_;c5d(xcUr9xVnoZTz#4(Tz#q*_EVp%Edf4360SZ@60SZ*60Ytf z30EH_30EH?30EH^30EH=30Lb$!qxjp!qxjo!qt07!qvOIBwW3VBwW3dBwXD=60Qo8 zaCJLLxVnubT)l%NT-{0%uHH@(u5KX-SL;Z^)!RtI)murz)muoy)tgDe)tgAd)f-8| zRS!wH>Lv+ST_oXZt(SzWH6-DxlO$ZNCJ9%oNW#?(B;o3Ml5q8gT3Ak9M-r~CB?(v8 zkc6wNNy61tB;o2xl5lkeNw~V4BwSra60R;K30Esg!qp`t;p$?NaCM=VgsTf`X`$;# z!qxdC;p#k+aCI(8xLQFHu9lO8t7RnN>Ku}Abv8-3T1pbGmXL(2#U$Zs5lOf@lO$Z7 zK@zSOl7y?%Ny629l5lkzNw}KlCE@B6l5lkrNw}Ir60S}p30KFHgsbC7!qu@P;c7NX zxH_hW<`_*9u8txJSF=dM)e$7&>Tr^9br?ywnn@C_4kZa!hmeG;gGj>Ffh6JT051tw zGf2YKbdqp2jU-%6B?(tkNW#@5l5jPVBwS4(30LDu!qqsEa5a`BT#X?KSEEV7)hLp1 zHIgJ;jUWkE!%4!`Fp_XJlq6gYAqiLelZ2~P{Yb*qz9ivlFiE)TAPHB4NW#@VB;jgr zl5n*bNx0gRBwX!560UY930J$3gsXuh;c8csaJ4f@xY~&%TQ54``guvX>Pr%?`jCXHHj;4FN)oPGNWxW>BwRI~i;pShwB;5QnNx1nZl5leyNx1n( zl5q1ml5q2PB;n?7Ny5!%Ny5!vlZ2bUA_+I2Aqh8sK@x8Mj3nHAnk3x(sS};r{7Dty z3r0zASN zd$>E`A-30pY_1170qWUI_p^QOWAoh0cDcudI9T~V8}pjYQ&cmg|B|DTh#2gs(4CdYiubRLaz5WgB-zNh37hmBv48(nx3<1EPILZJbwW#wzXREWLf ztwnxpG~X;u))FdAN3x}7Bkl&M^dlZu-vm7{k` z3$+%jInFFy*A!sxJH~J6=IPEDmVSSj;cxLknBP226qx6wNz0n*%s(j7%8fs6GiyI? zQv=3I8$GRRQ>*3jkJ6(}E!NAMrDvK-Y^E%!NmCxNUDzkR(p2IzyT9KXx0Ti8vdDV} zOO4_)Y2AG7l*=c~M|!QP&gb$_>CL7Vp9x*0x3!61`s`jS9c?P{js8eFt;G!Vd-sI& zZBu|h?r{Ej`?-PAW$pWc_O9LiBx&2~+6O0_8VNI2 z_52~``8D)Kr)GgWrF94C#^O!b{$+N`FQPM3oeyxY<^@#@}QLE?_Ry~4DyZJqYo zrSXlX&d4)CGX?NE5uLU3#W-awlqaEFrcFkJ^K5wRH? z9FCJ_H`RAp*+rVKt!nJD!y?Vq>@Rm|lT`RWXgujy-qg7%u1Y)**>$0IPgvJKWofat zEwbx%ir)g1x&&58&dA6}5>JbsaoWK6u5SfNOVLVQQP*W?C0>`K^?6+(wmsW3T8ppl zdOS#7m5`JkpPC|$_39dd#E2B%tkA~7z+N%Z25ogr;El3^;X$er;kO3%7Vq8?7_Mc<2cr3^ z#geGby|v)Xz|ZZHQv?Juk{kGWkW?!Mzt#&4S{e9_U2?&L1j5!eC}bl{7l@nQC<@;1 z60Suz2cEP`H;L^%QMH7Yz$0;r2U@o_nz~)KE1MG16H`TDZTBcGDWKcoIOP_|>cr`n zX7|-XGrN6am$sl$pefysM&Wg<2>!eq@7oQA+zjpQV$9>&Xr20Q$L*33yYJ5Kr-e0i z``9k+5T&)6ZW+|!?dX8?x*6UCvoHMc%G*kO+9dV z8gEUHmt*{&ak~1u!q|OkZcJG#g8v9!s0|v@<8?`S0>w4J#Zr4W8im(q(I~t=hn~^{ zj=oVd)iK^A){Gq6OLNrs_`nkE|)(kXde9z-S(yL--Ku_2wwT1Jm9TL*sg|KlD5vr@RTlA>5LJ!;Pk1xV(c~@_Gcf z-Du=`nb{58!Dd&-!Z+rYrO+{mxcO&{_PM~f@+X^J5S6txW!NVJ1x*Ir1t*6gi+$e zz}`_N&{3S^j zwE=(j*{@=33c^$TjfM%jBUP3q?XFQlj~HdypbZ-v^s*vdYRV0g17ukigHFR4C6ojm z3zB6M8Yk#*gk3ft?&BKieYzcChI>WAo<0k;aZdz2r^vFgsUc`qe_6I_#V3O9_mO3c zx4f5+Sgec;6N}XOFKcJpf_m5`-$s)I&+aE|XXnA@0glDt7!0?iJFZhDTT`Y3gI|ZH zYRBau8+e%9H9kHyK2dynvM66GsdwBTr*uJTcf5Nnq=(4P>x&nl)p5!$^%Oy`IM7Gi z9FK;9$Q%d^-fWb5YxzBcA2TUncXA&zTrhG4p(F6>fEj|(Sbfpy8SjVA#(0PrJ1#g_ zY#bXLrj4%)*6dO!j6;nuad%2ih`9foqI z$fe!b5`1f@6s>jnBN*dqlx993eBLbef4(moB}P<^%s}T4=!?!DCl+m)7b;2y7f;l# zkMDa626@!0FYcBgc7Nr-yPn(kB**jWzNlMLQ+?m)ep0#?^LpQReHDB;$Qh!(s9&s> z*wXh{5C(8lTi-2V(qL_SV88hmX+V>sAMQ9*toouq-j(=%@ST|`zaP5VkfzFh7<`AL zj4bqfBq{iQk;mX|?pG5gjny`O-S2vfG`gvcMT~DW_1_(ZQMf6pf3`^(2hAKY?7@;^ zZBBgu`XKL++#j{(klY_dVF;GJi7Gp9) zL$z_aA$#o7Y}`BKkua%3dtzeux%kRxFrna9=8Xp4f+6?u`UitY#2An6z3ux;pfv5u@zRb)f|&WeJK{A%Y_#gS39tp{;glrI_&1ICzS>(68*$ zDs-t(JnQu$!&Bt!z}--Bx8iD&mBz zfttBCEiEG>RX7KQ#cAo4VV{IbZmp{bJ8KERP%C@VViV$15;BrA($dA2cOqi7!S!Kp z+PywBZJIqcyiMZJD{scT7LFWs z=yTzCT#mYkt6NhO5|S`d&sfqC)vXLaY?tm3*DuY%I9MJ2o?Y4|3QKa(1?s}l1-7HD z!qJVkG_{2P*h{)gYdaTiH!3?&{@r3x=deCn-?s42?9x4rrU=~YUih~N-0OaEYD74D zPQPNMtlkhY zB}RH&tNkS623dNbsWk#G1P9!R>b}x5n!97;;*LNQmPDcnpNFqSGCn`7PnSNTJzf`C z$kEq-Pbq3qABm2ri8t@ch2w09#9+Bc>^)fO(Qa;zTw^!wrDX@L&yCemS|X=KVhFzV zLgaGA_c>JP#ngOk;Y~A-nQYMsjkJ_-G}nlo}9)VVq-g6b2T24Mo8( zyozibccakquQxSB?dd4JrR{w!YLp*GTlt{yyEhi*YKeL;NIE1kf9VpUMYKg_#Q43l z-_LFG64sm5u42;UJ`1!Ddq;=J9BJhv;`Es)e8D)P*Cb&GzHdr&sjqSrf_Kp~qVeR% zVHaLGBuB%F?+ayqJbZk8^eMZ1GEoej79Xo6HAJ`ArBmpRRQ&+mF&ZxCJrsjcIR+K} zs3|_?a**GrNck*1Ej}$hDLF$_p?7P8a$^qIrO$D<7-andcZ0st3PldV%!?ryd6jRF z^c`#rHT7I_a(rTPy6|Zo8s9h|7WVsI^!#b)S?&9P*sgZ(kQ|HBenfBQwN147mZ5Dj zV~0dZKWmE?#NKXFzD0Sz;C58_6%QACaj5be(*DF#Fzvjk`+01r7TFT}fnB-)f5gO# zV%+huu_EI5*f4E+TkI2d=`Z2jQP!z3Fs?2{x}-f85|?jN{y;I8aqqYbL(N9aM;MLb z!V6ttkIJ|Y?6T1$Hf_l2BMx+(8zE*T&K|Fc>bM0+SJ2zz-s~?MRqgQeafv==ql~;3 z@!_f%yn4-XUj|7wQ9fcGzUErv0wX0~Ez1_aSFsy0S7h{yO^J^O6O2y~ug-~!)dmK{ zgUtGikJcw(Fml9WFbcp+ACHIV0O!MNM>Kjo8ra@c7mo(+j8gd7%cQPNt?>_-{CaFJ zboav7a_^^u@ln4tC1au1z|Z3Ftw=xIN<>=M!LS2ksk!M@O0xn#ymVEu}FrT1tJw5e)ID zP{JEAQiOK!lY~@R!K9HfQbcZz#{1WraKdhmN=eFq2R=7EK3Yp@OE?n5;r#ufuy#Nq z99OJ3To?-$;z<0$?)_{}#GN^!^O}H~B+lw@PjbG$-W^OPRr_?g;ajm=OIe_Ov?8&; zkAj&$V={UN)l*POUenMq6aTVvU$ZfVkOm_gxn10tK^Q<7NEoDet&fmbI+SUdgkgG) z5sZ#xbR0vY2%`yO2-$i8<8+k?3{B;BXk!j_xS8BIiJMrt1ZI z1v8n(z2rvjB{vomN|>#Tq1n1}g&w+&g1LmMd3xev#upG4>S@avT|%fNEWK8+f;aK1 ztYT;_Rj$`n)-r7a({9ky+>CnLyqY1W36r2k&O#f#8*sQc+qhBJ-blfX-ZX}8W{8`| zjkoG5byq_aY$0$jxp6Cjd&!O4nC)(c1Yrk3-&I~^ypQpF^kVAu(8ClwKzQ)lv`3is zC}Agyev+Zbb(N>}&~6HL5uUj=?OCQhM|eJwCG2I8o5_uuuJ{t8`v@;woA$DJdU8_l zsy8Uw&)obCjq!l4bVv^!q2RC{)i;eBPf&1-@UAZ9E^*^~*G5mVc(33?N# zup2xg{(YurIYY_kSEH1kW!hJSuXO>pjT^a@-1wcI_7kH&5cI9&#@`s_R&wJny54Ur z^di!#jDJw_C*iy<{#y^}Kc6uvP%+`J!6Xr6g7F_c6ZeUm%mkHSAy^5vYgM?F+@$}I z#uUIlZ0bPhNQu6c+!Uzi>#m1->Y+Z&(Myjy^iV$vg7s*K9tzV#{7H@JpI^^lhp;yu z#k+HBxhY0h^hV>EmOyD5LrH{WLW-{E6{IsQgL*?58c5);X-tE4J$g42J)UWpo>oO4 zXBw%;$LOK)6l4>|UYkajXYxvO7|N&0B*J9E6aqb-DNoNv?`ERMGwB}BL>Fh8srNrq ziLPBjNhx7A%P-djiX1LrW=GqDR|*i^?3UC|L22psGE-nv!*t ztocXM9=(AAuKAeg0Zsa^4%^e{@l3S@7xivp$V1S5Uwaxoo@tX_G98rDz20o%&itglRc|ukHNxwJH}nz@ zF?xXT7UAHvg2POEhd|$FI!b8a{pj&by0dGKzDEHao9QIslwQ=QCNZEOsbdv?4``xS zGqn;vBk2Ck^bMn5628)N(u0}k^Gx3oz9XEwR{A5;+F0`M4AH-tej)s->v;u#aA-9B z2}Q+7@dd&~!e0dbRK|3Pa2cQ&2+}_#D9Y6k1!jWEqI?;me^Ye-)}H3aG=HiDG86!) zQ0U*3j)YEx&IH}lDf~r@(v85M$|(BJ1C@S^@~1Ki-JKFda1eC=rbIK^pAaG%rYCi- zVlYy#wO1U&v{+Wj8>K^25(tTeBnndX9H~r8BhaBKq;Sdr!a%PcLxTxJ2tx^(1X4H! zc?TGj5sZ%{WD!PDGL9i~I7R1h?P=qgHi0lvtS`iHJe}b@!Zbp@Ua=xZ3kmdQ%1m7_ zo6%x|?!pxMCuI(yjG())cELQR%_m$(SU_0FYA$DAQ5G}4giuM)xtg+u(G`T1gjIyq zdObEUx|Tpcrs!Nv@d~_#siwfmJ9*P;8Fdld1P=rw+9fwKPH(2%Ot^(`EA!pK5IvW& znNUaALbzS8#~tFM8A;u%sC^$LcN278raZ#v{e*e~y_7;Pr97mU@HnH75_Es1Ji{oR zl=39uDT2<>+NBLl<8SR^g(_W?Y zErwnvyg}GccvDxVk5Ud2-XCTkT318@f@4Ox9D<*zT_=a%yUt&bhG3|Q-y`AzSp^fkppIG-|${$So zP0xFY(LV|22^R<#34i^gVm2aLrCbJ@4FuhvnXQbP2nvDz%&hA9yiyy}dXIcm$ln_P; zCqxh;38ZUg`Y$uRn>m&cNBHN5f!Orn6(=zQK^RHMx>jWj)3OO;2?67XbZ=(zHFLgLU!2rWr!VGwN(u<{c;-UF z48lypEJ6{Xn3?I$%%uc6JM$bu89{e!?JD#5F@ZJ4ltfIF#wI4EB%~yyrX)uvVE#2B zAsKU1K@Ev#f}{bMlj65KCgfXr9V8ye=h|UgBGwKEH=2@~`eHTwg}zCzS*4-chUg@x zQOQ8jn4FJKx@`9umJ*AV^~8+yj1&?7dReSCDD$2-F=sOzlNd>u)EJ350_aIum?J>k zHaaCWEhQx(4O4plajBS&X-L9!4CnHxm5ti)Iu^A_iVnk?dV-pKry`Bl4*4c08>LZA z0m+!soT-!YHAqfc4C4T?_vEey-x7yd|9EYkJQr=juIi*w(3S9Q{k z#wpk)Wvmu{@65SgyCE}eBi6ccpETU3MmVD)`iY2zbCzp&m88wLOSNc9N?hX0Lm8Op ztWG--Csj2yq&0PsZq~H@X&p?Mct3bBE!E7GZX+k((@=|BFgMBTCOlO-rqFI}%1nQ* zx8IhBGu?wYh4$7W$=W`J7G0EnT;kfc@pe)F-2lu^m84^KYOAQ)F#y)6Ove(;9b)sa z1gxx7r(eDiu2`V#J|2$ahy_YBnGo~FzU z*x`O8J%|R)fZ9W1UB`jEOU6xM{*SZ`a1ZmYAhs4u#`fAp?Z`Nxa&g38LdIm6XLq_X{bwU#)omz<4pkraG$4;=^1hCmvr=l_yKsa8er7{QzEdA zT`+&ZPP4KL3S7$`fYP4F8^dcO9uAo$4R>LDkND&9bj-Up4|p$5X+j1rT~qL4qiNu# zNdK3=Ep(6Z))^DscGh{3<{LHe7n8CN%CCsPq zJG^~hl2v&Pf;aJW199KC(2fJqRtK8e5apz}kxSKskaV~ya}bg+9d6`w_#jk*)8P;) z$B_2E2)|`ej8JbG6s8r_4|+C4`cU(Ea}av#DebQVgNzo=fEzg*j#8wL@E9!NV{vKW zpxDO5!I+W#6fgK-+~||0+`+g}E7UmyJ{YSuUuyRI2BWCYo9YLnH-C-R8I0WDpti`N zd(KH0a(VHuWU}^SOQ9gGt zrlYHeY>Se9*Is#YNTx~pxv60YZp7(tWVW8iRB~!6H^Kz3t%%mrTZSC68!x0L2z60K zl$OvsFA52-)zC|J*(8gFO>_H*RX-Gli(Y$Yg^N&x@P-YY z2cZ$q=+(j}T=mckaU7H}i=uUVeNIP7>$-dBIe)Xsl97;_Dy##`;N9LVLnD#z6 zC3AqUsUy6y$k2vhU8W=xQ_P)En@p^;bZ%0@QyVVk9w?=Enz-ls%s4GM zVA!EJsaI3xFg$G#1bxNRbBANEO3AP@aZ+$o9nz)#O$`)>LL7nm4tua4R-1SJGOWPY zKOAY%iAfphNvY}Bb=2B_cwFP4;Rk}GSagHouz9>@pEw*#Rxwaa5b-O9!8PO#|HAH* z7@NY4T`7qvsiLl87)GBG=HtZla5Mxbrg=?8mmYqU%h4xZA0FkaVC$tR1C^i*17PcL z)NfFuX#}E!(ZzWkf?JP39~lVMVIsUWYpgagcSK|R7BLDkA;=Ox+?s{wtQ&#n9NE-7 z0uMME=^VaT$T%@EVFXrE+eX~qPs-6=>NGMy!I*ZW+sFh}!RB1kL^0@_k(`Ae`9=F^ zHWD?NtOblHbIIC`vq!G;^NwI6QPxy(dT!Z6+Ro~cGwo8I=viHcx4CX4Tq4J_k*E=N zqnf7Usi<8j{G&&5$HB-GaSA3DOtVCv8KbbrCm`#~IB8~6X4W1Ya;WXi&l+V>iXoqk z_c#j~=ZMgZEcA@ZtaV9PZGQ5hENr{0(C&RCYqm`(gW_Dga8%^@#Vg0JEYx~|ma%cv z9kRCL=d8IClts|26tm8c;_A|w zSFY*$jEwZe__PGE_~9-wT6*)SLqS+l&fCR8T1Rz_ly1Pn&ggv#_iUNg!%(Af-zss} zjbrFnN5ijH8wop)QC8pfbK zwnM^!YRskx|GN(tdAfKV_?=L~)7Z;)->1`JlhaaD(vmU~6UC$* zurCp+EOCFluF&0h#44muOHT|$fYr~YWZeQ;fEoAm?(c<-aG34NIblK{$ z=(3Gs*T_U{P^ugIb^8EEjXmg5yzWI!c-@D$Xe#aKK#l9f#ys#IcJ7-9-GU zx%EQQ@HI@$-0?s%0F=#@o2*xS|mM!_I9cPe1Q|w8vLQh^3>#7HS_WM?1T}zaw10PU*WAO{0)09C$m#b{3cxbLsRxnykJ&-Ms5zhR5*{0!Q2<| z@-hBbqbUcMzi~reFNy0Hrr;qQId}*zGv{EyRSaVHE?3KxI zdO7G}7n?8^o7vXEBG8bPnozXbd=z^}pYghD+Tv#mtrFBPpFs%prEU!Jc z%ah?(+j6njv>W7HV4j5ZAn|R{c)G|*$L$tJLVOZ-&LoM_k>lZHGbbIgdsm*R*ca_F z35EAY<`A)MXfEtqHwpF)73cGdu$iNN5;k*$X{*n5zRAe_$E$p?vn)QhC{WA%t8=qa z`{?ya;l5atHnpHQQ)EUOJ_^6j0ikG}1VRyoHk!096vKIS*U6x8G1}5@lW$Xf`{Q=8 z$%)vyfJORYVH0At498@2u{gBSWVBKOT4^$>mncT>pEW=mQZgCNCkfq`iOJ$vUPtLmfIW~o~O+L*p z<$z7)1&uSO>B$s*!8BM>kUP9{k z9Ap`pkeHI3nvpC%*RD6u)pej?5dy*La&eT4l%R5uUwXwNV_qK0;n~L(L zpc$vaV@w{r z+$_9;c_^h2a()2kL0$^0F%9fODMK%2S~+?#uNC6r$*CB9>ht=9f=LW}Gw)@qG6#xe z6I8ekkICx-+>Fo@K8(eaMK{;Co-=~aYJ7Di98KAUX1cqig#8| zB}1F`jor8^Az9oTmWLs*ej0{AekN1@dc4on2L(~q;eJfJK^)jVJxGY{(<2(k=VQAh z=|;ZP4_~$E z0WbfI-RMlr5Yf>SW5t(I6T`F_x%rRbGaGfy$FsOJ&x*OSq?w)hfBSn$Nj_@f5rIvG z_#Uax{}7woM9@QpU~mojXM?;u+n`~+IX*ojDG4(VqGb`RKCm?(vnjU-dsK0lX#T7G z0qt;Gewv*--U?9OCOkv|?zR~%hq$>eEh!ayt)%w~619Pu1<1MuZAi`AQ7>M%is_%` z<8!;R;IkNfZu{R?0FMqjqih$8qsL+QO?|(=68DHnAV+s$d+##pz7W)Rw zKm+6!V!q*7v`QiNW;Tj#ujJD=7UG3#5{I76$79qNer1=n6cPF`Hk>Cl6yo(GqbbCl z_o5XFH->Yk+n9NQkxk|qoihS)wmfqnMhxhS7C`6$om@Z zIRiF(15e2~84R!dOy~6go_GeH^mXJRd12ZiJTb3_MMPpJyrMJlioPS>ujvE}1k5}Z zryPWG3tpRjy~sxY0BV**_6G&dh{w448G=E`2JF{xuz~=9q<6JB{8p3;92S^5-ID=L~R*l393> z$v~L(jA$7`V^q(=%*JQX`dVa69{Q5@YQwDI__7!E`I84>hHe&S=+26qJ~?^X^p;sq z*rjj9!Xey=**fcJf9afd$y6lyZ~{R2USz&8ySI31UY8%WfDT2!_)9;Cqfy=P6Ania ze$Vlvcyf6m=R%4=McYKngh_bEGK=tz{UqZ4?#xbCgmL(1@lNGLxWkg7kB2J1pxEC< zpLg-eA75XDnKNvOQ_hQqm%>qjmLjVPAtlCX~V_T_s>#N{AZDv66LvhNVn87uO@6(I;qp9Lgv4>p}lYZ-j zZk1d7C1SG3`Kv1&Nl9^QoN^h;ipUw=ElArwx?7;64cJormcMK^qe~Qn^O#lf)<%Aj zG!%b=m_=;f)&q~*TwL8Ba|Dl{DbDk0pYQh(H$)Xd(pHRy@NG1ez!-j_(r* zV*86Vw^U$i&{47x+vNVO-JxB(DsQKrn-{NHvC$w?yeGy^j$RPJtZ5AiXAm4 zgDF9W?j~-whc4qT=S_B{ds;faEdnO@!Wh|7a>Q=!fw4IwJq5qpSv0FVHlwzd;B%}e z`Zep=OGL~KO>G=ddN9b?Cp|;#96LK&xW~@M-a|(zItF*tl|s)U;@jro^DVRVyLOVp zM3S7+Z=$6B+V{7Vb~KUfm_tN|1EGVpVRfaik?fRw7@DdLs4vAW!$iy-k!9MvhSIya zi+;(j$irGob7>Mc(9eA3pgdS}dVv8FCPOk3oby}h0C%tj^SMe2^YUD~sm zvnRE0$D93d7%0!@`di zlWrM{fwo~bJ`vKlpDo6BN%L&DgAB3dSSY%E%j^$>j000rMAgo@apJN4$*Z-e+Ggj2 z-ZYx#pvb|(_IF8dF`_UzOdAt0=Y{r}5lTpN=HRs+fZCAJFl_|hXkJH((u-kej_Nts zLY^fy`6tj@&Ve%-CA5&37*T$7K!jFUKj(Nq%>Bo{G3S_%G7RNp3ruo7r)e#7#aO5}#!veTSOX*)d9 z*T{*KUwpxS3Kx&W594yn_O%nAGL$++JFE&F|9o(ppHwE+ z{xEsCHmbV(CD1H!ByLV`@w;(Kj#gP$zSR!GRq))DTeZ6S@^d6y>kpLISkzKH#{9$- zG3d5&n3iZM$B4B+H0&xEsf}qZ-{alKX`auur3C~oi<>JM(s%Zlx%h2fN?1nFclMaM zK5pi!xOug{HnyJ8wFLcKaB~f#8wlQ2!fIU^0#Q|0;m6a-&9_ovBjHBEO@y0uRj+gt z)3_RL-b|=tw(ShvPS{GgL(g$Hqk^!5aHm%wO1D+5D@;;X zhKog8UBv*=wza~+kgZ)bL=4+jfi*zZDk?;sc+K6_N&nRHU!gp)duv4}DWCW5hE9&6 z$F-NWkkw0U53gZ3TUKx6wYxXX=T^P6+n746UGpAq4a^VW0o8|@p4OiJZ2Nthyq5^? zPJ*jX_ZqcdB)mj;neYms*{fdlhJGvW<^dX3eT(vggtztRVMgB}aBG2jl)&vzDo+zu zxt~C7w;!V?^+G?PfLjUF4+tL;KGLP1b5DT!DHBf7XliW0ac*EL{j)bwMvi zxp%fu%*Q47Ry@x702zL_h z(xu-0PTpG7>uC?_p+~6f-S6a8d4kbL2|Gx*G`S9Ib&DvMX)87ed|x`&}> z3C|IpCo~e8^gHa)_4YBu!Nl?+;U&V$y54KvkRH`X8Owg^y~%x4mbV#vOV^>Bvd|@2 z=%y^~!wl1o=_)5E@b1#|KHo`3-_xZhsr(VsKG4&=(T^$Mb|wqGl*Kz3Re4LGU$T6z zYkze$L;>BDE80ZDnEOb3Fj&Ojqp3+554&FCQKw* zE->*T6s=yD^f#lI{?YNKStX=dWde88wx=nK(sfzsrmS>RRx73SOIDxu0z~}16=79O zpcAroB?MBbCqv!!yLh9$nAV%p5QgZ4tPVmjfiBA0PtOr{HAF!;A%YOe0%948zM@iv zbI=%%Bg7LD2#JIwLb6^~Dl-pYTDqR*jSkcWbYxaKGV4&LjbUgQfsV{dM`ooXvt|)S zu@tY7+ZE}_tn_47{>H}IKJqh^tKW`}&8j;xtM0z6c@#{eav?(ndJb=N1_iqNvKCRL zm{7uj(OOFJJjUtCtYw69LWN$;0!HWS(M5V_83l_8O9+*^U?rovBeUv`%<8rH^^~pw z46&|da2??W!g{>~uVOXRoUEYk$gH<8%}wx#E%#USowQlca|cuF2wMo=t(olhR1%^w;bOri^NYYjQ82n9OuMzYVtJgh3et7r z1zR8rX=5Qbh_~Mx9^4o*_ro|&WSXn+u{Rf4t3_t|7|eKN&c%!e&c(N|xORi>@*mYC>6fM7c?o(J{6|K{{_ZI#0yW?<;y1DoS^N8GWy&Pi4xL)^) z(Gz-wYiH}{_6U-1OcwJGbc++&2fBr8Gn?mPmG&m#>=cJDq?Wn(Lb@5VTdcz^V(hJX zoVA!cJ<5NR^RZITdd$CeuQ`^~N0dHPF<-pBZ`=Xxm0t5Qjmk#U<~Ggbn1_6v_2jh< zA6L9`u5%tfvbLh37{5adcIIRKse0bm{9*5<^RqD5Q#TKDJ-4Hj9iq90zjAM$hq;nF zg*1CEriNPPVW#{pu^}XMs5YW?-ivXP@Hpl-Ij%d@53`7eM_h-qik{(FMeFaA&3k!R zQMc8y`BiP{>;*Wj2vd92@`W7P{05FIx)dav-^6)EeR{~|w|QXEz@f7F2v00}I!ZPl z(|pSp;>aRwx2ZPTqy5Y${@a;F_r=NPlRUIYxmh;1^3mAmw|UTy*44*?d83IlmZ37yXH&i-O1an=k*@vy0}Y$*KW|7k#hD zs_~!Gi|k`%wM+Z)MVEe*)nuGsbZN7!4#ojS#w=MK$rFsu?~~ODIKrs3zrQ;9zno#T zVg=4H%EcK*mxjvfR2*V7zKg6D@D!tlwX!-3#~4L@B&&;fj?u9bvU)uZGUB~9K5zeS zpsa4Xa+Hy}`M(`ybo{ie-pjL$9#|!-4{JZ(y#|LFJ%Se^-~x)+jnj;H(?%R;6x2yp z_wqcW&j!irOXxf6&^KPba-dQCy3<&JM!PYw86Ak%SM>vpj>XC9i#X9p>gK2J|1U=x zy%#L22Y9AYQIM>@t37>o9d=h7*6#mq9VW>RYIWbQ!=XlR|Hq+5+Mu>|IMwLTf1GOc zP>`y=^Y3Gg95-O9;>eX_jpA>>B+pS)^afNAQ@y#oVyd^Y(4^h9+!{N7@e^zNj(Ma(%X{@~qq7la^$V1A z?#kgt0UO{zu*B@xfJ{H=hZ}u?8{*Ir^(XywqeFPQHXLuX0)6NYZPg zoNt85T=n;Voo{p`$fo}JKMpv;vhPunI>U{iSW|QH3Lp&ZFSJ z|HlzW2Vm^WXr`{1o3$9=p1lFALRWeNvVP3byFvJZ`j2yt4%uxM<$oM>v_H;l!SGw)_0x{tff{}{ZNbuX8`PAZ z*PM5B2-5D5_PKK4QM?mNGC}B2PL$|COYs`4A9w`gS};p%>3`+KqdF&6eL}9BcvSDi za20yx#G?jE!qBBqfH7P@@u2vK-X*9-?+ZC*3s{r|ysEx8@3tIAAjNc+E!S&tCpbisTZ?as*tipsa$Iv`o$5WvOWvl~2uze&6r8=Z^O(|9sEvGiT16nK?6a zKlc&IG7l6aKCozDka(RB4ic{m07Bw*AxKDrTT7N$edxu-Kp`#F2VGta6w>0n`o*}V zWvCyrkSr_p^lOj68Oivl8`K`Q9&djS=Ka0LR!Bh#}kYa1B7Hr z(x)9=lCCyQ%(F9{WLFMS-IDgk@#*>{sDl&%LV7DmvZU&XK}$hGS_cvm22M*FP)H4a zss+cREnD4LNE`9=w!k?9jc_Rv+F``Sp7kSSnD+t55e*Grqnf`+tHpQOfuhJ@W) z%P!n`ETZj3P4bG(+VD=Su`E0kI_yQOFdVX7c}Ov_*p`-pny6}*) z7*-cNq-BAUr9f|WDHdaLzV7v7EPzP+^6FzzJ%xgZ#FyfrD-y|n*)ju761of-9u`QX zuh38qfQiHs9|068q>E%J(R*Ys0~P5gZm(2Ok?aUjj>g(K9yL*^uWh~@T%@u*-{r`+ z%H$&XFGnw1jT-2}26!A`Bn;jbJJLCcTZN;S1xLs%I6`LYG9Sr&tuH>k9Bia7^Xiz) zS+J3^LL|#Kdfo#o07t_1t>wIcBc-7LwMcsfZUCctWLGAKQQU%k+IUzb%Xi=-O?*JI zT+*MfTmeAR5AeKf0+Q@2(CPh%x0}(g0FcBx{>daH`Nm;0@tR3U@{dE0{j*6(3XQ`Z z{o+bU3Xj`w9MO&p*or&2u7CDn93I4Pddi$QAd)c1TYg7*0x*rS+;B3I_QrvcbQ5?^@ak_9WNZY55pC}1Tu_*rD_E?81Xpd@?iGs5CQOHxrDACwjWo69ZSaY^4G z+K$7tx7J|-0#$Kk(u{(rMVI25LdP_;3sORrn z1Ar1nYPrLT`Yg#B#q5 zj8cQ2xBMpDX5Yn83SV1loDqvyi`Z|Y?h(UF#8$q@g+ zP4)!%NL~QPMYtO}E;eCGwxlx#w#-h)rLHigx+ENEh;_o0Rt8A&a(zdub)YFNL^?Rp zWzdvloXL_`nKY%yb!Tyc>mHa=)H=KtSnM!ZN^+upeCxWts%JbRt`#Vy@9_}U36@f8 zG_dvh$lL1*JiU_;F2$Xtlsh;XbL0&IrGyboPD7&)bufVBRIXyBQN+r-yj3i8Wzb`r zVHIvW=$1+0I=j4+vWv3Y1HyH7nQQNImWZL#%C&cyYwvQd>49Zb!-wnhay}y;p{bC< z#dn#D@A4taVUd;*q=;_Cl%o{lU}UbY%cYbuN;#!ML^FJ==vGZR!5ChKV@*=Cn&cqv z8^~wqb&jU*XgW{f(z;wrsS}AAz84toM|%7~xk$Mrf*2lG=*E?G`6mij*5#iW?Khgl z2B3@$KrE>mOOChb^FPWBk=9?d{z19zbW@tr`ZuM4@{gMb;Nbid5_l*~RI$+HC4%sy zRMF_B2)9=E(ax22#hccjOwo(vQ-gNk2@}01;%cFg-$#Aru4Z zV$_A<7EZTEDZ`n-D4Mv&u87S-Mr-~y()lx|#OR~Atg3*TkJ zv_hB?7$lCelEU?OMeH6bTwGVU{;njl3<{5lC`pucA_XH17vL4Kk*I8BkTgm3fW(Ba|Xau?S~)l+dk|;iB1XDdiMyEh?2FU=6M8&y*9w?G&wIFH!kQc$}rjY0B5` zZtUj?A;Xhc#7!p6n1gS6%pj;J0^PkLaAr^zYCAw z>2{sM4o|sZ_|kMs1R*2=$S3;zL!ZB$KC0uO3_R3k@K7b;VOV*vMwRK&TzD8(g&yqO zRCa)>RrnfizI1CrA>B%CLzBU1QCo?Ch9i4I^#S3=4o_vrsCJ-;K2hyLYbOydK$wE) z5$J9WW}-dl5h6Tbjc%g$q+>4%cN^8-ls*jIUj!UTw=j1rJ4SU7WiVxk@Eu0$BNX<8 zDtkheJ)t_9CiaBtNXp}s=ut*I5qT^f*%PWyQ^p97=V*Z8@ zsD41}yOj4t40d(uhZOdN>KsV4I@jHM9(_Jx(x1}Ao=}}n`Hu)^cr2tFDKY9QnwC(O zQer8~D9b4;DC`NNc9#6RK&Hbjl{m zW{T(&RnFhk?G(=QRP!7KOnYhFN!f)Xeq+uiw~j8LZ676@l0(U*Xh(cxerhZGQrHGlHdWqHxkffK-B?m{>(|U~}x;K@5 zm-;K^H;U-q93D65c9U|8@(1NMvecT=^cUrCN&`jA;(&N4Z~kw5GKUr7fi$r9FK+)5JbaW1psVghXq8H2RC| zj4e> zMNIS@-oxm|BNWr;MOHMj1(YT;yZ~8O^|B=<$^BFs#qeZ7h8!(lm}eu{NH@ zXDQE#(CoIfNtEX)lPO|)r%k8zMaoN*X_QFH%OZi9w9XLL*M#Zc^kAQ+y+MhdMSW9v zze_7SH7$zrw(yuk>wA>BtW zMoAa(4DT%>;5Pbhr({reP%`PeH|f#ZB(4X*o4K2P_Ce)f_2O#kS*}sj>QSXaa&$pGHAz{jnR&iK|B7;opzjz z-)!!5r5&%wFA%tXzaBa5)|>hBiuL=@-dyY9`aK=6ejnMJOERA$Yp&SC(KFYTcf20I z_{u|>dKY7nZa;v>>j69lEnLob0*}`NcwFEPJg!0(2b{#?6s$WQbP|tKaF*l{N|A!h z52GG$U`T>!z<~G}i06&>fCEtyJDqV9zmHani z@$YApia~zCW9rXh{wk106*ZjkY@6p{YAK_HK$ zPfNjAI@wE7tbi=#wvv>#x}KX3%2I2+JU<S77YV_B;`90ue8!0 zprw5sycNGjNK4Ob0%@rO;`^JVrHD;|8^rN~X#%js;AmZCOcho%eK{U#Kws~|0% zN1lPG3A%E}o!1~VGGy(g1R!ND)IZE}bO&q+Pe;MemXuJ`^=5?ZEoe(iy7?%58^bLv z*$mtgw$&9b)-z6jv@u@81a7GoWf&-UOJ|VR005Vm7k-qa3^j>M{#)?s4a18*Xne4w zgzL{w-U8$j{^U@>HhSb1q{tO{UPmA&M3D|*`5ms6FsmVu5iUX1ED{`6EPgY zAXDa>7^cV!YzqHJV3^i~NXn=B;gk$;m}2tMGmsfq&JkT+d>4nwmH`ga5*H3r6-vJl zAST{pEJ#ee3*cImmB@Do@{ezv#1ydu@8K#m42I!GIyp=Vme-X;Cx>a%j%(hYYh5r* zSv#=Fm4v%zoOP&JUXzoY6sGL%l9H-VP&0vHO3}T&GjU#I4HDcSC`{}6N=lkuJ|Yts zrt~`)rie_8d>Epm!m(qcYzBvkH?tKWCSJFL#DvX1We1Ylc?ZQ*f&2apEGFLXZg^!0 z7}Ev#d=45DBku!_i6L?jLPv=o-PA==^7V`hp8?2}2gibYfJ`Nbb^s4=IGeyB-DlWN z?1f=aR1TX+rtqCWGT9nIGBIWmZeu5^g(E1h7(vsAS0mFB{Z#f&K$(sprBc)(Ju!;b z4QDYc(5uk_Bj+w$jwAaKd>;4#vvMLS$iOm%@5*(IkA_b*d`=236L0wofSGu!U&05& zpq&Yw5oD%~D8hODpvP{Yna=6emfb)zokqxS1e)nPlmo+J_+uj$2XTKDfTp=8KG%+gBewAq%i34MAJn z4QqEn!fF2Q6EIG&Mv?v941sdf!M^#=?Xs#c=jij2m7eO zjWL_9>;p-lriOKMMjcAtHqHsQdT+6(ot4mZ*VjH5PJYhGWwG)lKQaz>4kmZHif~Fr_2yr8+BDS>u?j?6qSwUJVJn*F5-D|m}HR0 z(b9Mwh4#(s6L@`}#OR?umXm`n`)NG?95|1;huqXD2jr$_1i7iEmzVk!%0CWSGs1Wi z-4vCB!>rHZ;Xj2pNyT8PVvNjUh)E{9$(DoZ%k#+gsTTu$)G3X@oBo#rc+&*L#gVgI zM18?zH~Hr7cAPt-<4Y#HDKr;3PD4#R&35~;32%zY1-xl`Bk-o!T)>+mk@gJKh!JHb z3Wx;NR~w->72zgcLEtwGd=vf@Di`M)W}%YCvqUr`zPCn8|MSVmtjWj$WI;5Kvr-*zs#!V*0DKZ})P@7RX z46y}+AFo?cj0irujYuy8WufB^)C;efPK*-{(W=`KfS=F>@Yro~ocs&GaoXd~aSAP{ zG#G5*448!~8OP*zB6hY3a*8RyQ_e9#PVogeS(57nIc+Hb$SKbag@iRaJ-* z+Dw#FL?Qa4BPcE%iv-GPdml-y(e0ZH!E&l>jODbm5GTubg@Ib{`2 zH0UTcCSB&ra_aHul&!Z5}F?y{l&nf(1zT+7FK_rM7Nz`+E^fV!V$5c(W`n@{A; z{f#O=4BrN1%IiPKbRxdbHGZE@WRd>DGM0xRJ6-a@U(UFoovIO(-{?jhFJ#R)8YV89 zjC`4*a)<7uKV5$qr$4bv;XA2|q_x()y4ZksYN2-uumSI6Z5;3Pw#7@cAVD0RH~3DW zHt?N%-1$!7wjKDyN0BGtrnHt$z|-3{0G=?BiLs#|_nUyHcpFaav=V@)%ZShh1xFCM z?OlW?yA7*5?OX{@b+!Qp1FoJa^6US3X4nC>cm#~6j;@TS&?AWKhj$P@#zA{tI|;^9 zT@Oj?qI<_3L646qkk-|NJjEZuQM~{Y@|1oA^OHam@|1N1b12M)Y)mLfK%Q_?Oyhw) zx`qhM(|_?8f{|=blk*f>g!xA=lk*f_1kMx4x=zkhL=j%D-X`ZMk|Fz`(kAm!Vj)56 zj~=uLsfQtN!x57q?O{A$Iu0^OPkLZ|5qi172I?uddoc#7A*hrowg5?cR4?gY4Aj#w zJ#1hxP)`F9`wN-xH{$vIFl z^2Qv>EI$+up!z-2t;(48be5SG%fP2+2_9$6()=%E`T{e-Dueb4a)d?FW&rwx8L;*$9N)mx zWfHRt>{D*nl3E`?+8op;-x5^dTgdB0Ch2aTyzO8!BN8+Cf{1aC7 zv;}ZkZ1SIMr5GuenEWSuDfmxI-T6;-rE`o!eBeLnf7O=)0JY2&0JRzEEkb&6CIKq6 z47It^1VDwCfuR#G_)h}@^@$&pWy+o_5Fx>h0yV#^+`H)-w39N_ZlcM6N-x73x>g{d zygTV(HD&L3V3{KGRM|w8vm!0oBsRCHF%mC zJl!SuCJ`JUsF-qqptkApvE`U3Y|-a0F9!%J6}fT}6v?K(-6TO}m4gJ8;X;D4m4gJe z<1P}Ey&Ru%OAGi{eU3d*+vZH3zo6$rS;L_vjCTy@No=*W4}%a`yb z?$fV*Tmcpo=1W?($%2ZhDB^s{!GemfK&H7S3o5-Lg})$hu%NOk`trvE4i=QH0%OU3 zlLck301GPLods1_0TxujJuE2vLQFg03WM^kTx-ml{3}t+LnaI=v=T6=!x#YNY^e?exzKX zT&4U(xklm1NbLqq+-BE)rTj+uoq{uxjw6zeAUElDi}DA>*ivtx$@p6pOuQI z5Ni{PI5A}9?z)wG>Q+x@s#cX&3q_z&tiH5z``l`!c#FV>M+>^Oq}+#+Tiesriqe`g zt_`)Vh+=p@K(_}e9Vi_ck2~j9e@Z7xXAvWq)&NQ%rJFv#Mb&fOl6J~Ob;TUVAn>I- zs_P3(k5217bB=@6bpm()waMzT9e>*K*S37nXH8aD-SHLp*{H$l(hKU3kLEO z`Pq3v>-rW=zgFP7s(|aNbK<(H3XJ(|WEB$r&K=iP1zgt!3{vp&{N5GURfRL|KcHio zk%b}n5_nxz;B{dxtX+2FbydarfF)@1x~edtxssID@;<=3>S?)Z61%El{>e$~s>XNR zH7BvF8pFiTPGVOz-X_e9!(U-%_UpaKuIhG#3A!P>s)6kK-HGg~KFGM~q}>Fws~XI%dfdt_7iQN9R3Se_Q*=j|`c9h23;K~p1 zbL9tUHQ)!dLZKeSl)477Iiu#4ONP8+9v$%-9=Wv>s*nzyO@KgD%_aWK#R(87?X2J2 kTw}3%`XjMG6C;pSgQ2#Yi4m~X;QK5{U<3+#N!Eb>2cF3I$p8QV delta 44012 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 diff --git a/server/src/gis/dto/update-features-batch.ts b/server/src/gis/dto/update-features-batch.ts new file mode 100644 index 0000000..2c2e9c7 --- /dev/null +++ b/server/src/gis/dto/update-features-batch.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsArray } from "class-validator"; + +export class UpdateFeaturesBatchDto { + @ApiProperty() + @IsArray() + features: any[] +} \ No newline at end of file diff --git a/server/src/gis/gis.controller.ts b/server/src/gis/gis.controller.ts index 8d3e357..8882307 100644 --- a/server/src/gis/gis.controller.ts +++ b/server/src/gis/gis.controller.ts @@ -2,6 +2,7 @@ import { Body, Controller, Get, Param, ParseIntPipe, Post, Query } from '@nestjs import { GisService } from './gis.service'; import { ApiBody } from '@nestjs/swagger'; import { BoundsRequestDto } from './dto/bound'; +import { UpdateFeaturesBatchDto } from './dto/update-features-batch'; @Controller('gis') export class GisController { @@ -22,6 +23,11 @@ export class GisController { return await this.gisService.getBoundsByEntityTypeAndId(entity_type, entity_id) } + @Post('/features/update') + async updateFeaturesBatch(@Body() updateFeaturesBatch: UpdateFeaturesBatchDto) { + return await this.gisService.updateFeaturesBatch(updateFeaturesBatch.features) + } + @Post('/bounds/:entity_type') @ApiBody({ type: BoundsRequestDto }) async getBoundsByEntityTypeAndList( diff --git a/server/src/gis/gis.service.ts b/server/src/gis/gis.service.ts index 670e26f..ebabaa7 100644 --- a/server/src/gis/gis.service.ts +++ b/server/src/gis/gis.service.ts @@ -119,26 +119,108 @@ export class GisService { return result } + // async getFigures(offset: number, limit: number, year: number, city_id: number): Promise { + // const result = await this.emsDataSource.query(` + // SELECT o.*, f.[figure_type_id], f.[left], f.[top], f.[width], f.[height], f.[angle], f.[points], f.[label_left], f.[label_top], f.[label_angle], f.[label_size] FROM New_Gis..figures f + // JOIN nGeneral..vObjects o ON o.object_id = f.object_id WHERE o.id_city = ${city_id} AND f.year = ${year} + // ORDER BY f.year + // OFFSET ${Number(offset) || 0} ROWS + // FETCH NEXT ${Number(limit) || 10} ROWS ONLY; + // `) + // return result + // } + + // async getLines(year: number, city_id: number): Promise { + // const result = await this.emsDataSource.query( + // ` + // SELECT o.[object_id], o.[id_city], o.[id_parent], o.[type], o.[planning], o.[activity], o.[kvr], o.[jur], o.[fuel], o.[boiler_id], + // l.[x1], l.[y1], l.[x2], l.[y2], l.[points], l.[label_offset], l.[group_id], l.[show_label], l.[forced_lengths], l.[label_sizes], l.[label_angles], l.[label_positions], l.[year] + // FROM New_Gis..lines l + // JOIN nGeneral..vObjects o ON l.object_id = o.object_id WHERE o.id_city = ${city_id} AND l.year = ${year}; + // ` + // ) + // return result + // } + async getFigures(offset: number, limit: number, year: number, city_id: number): Promise { - const result = await this.emsDataSource.query(` - SELECT o.*, f.[figure_type_id], f.[left], f.[top], f.[width], f.[height], f.[angle], f.[points], f.[label_left], f.[label_top], f.[label_angle], f.[label_size] FROM New_Gis..figures f - JOIN nGeneral..vObjects o ON o.object_id = f.object_id WHERE o.id_city = ${city_id} AND f.year = ${year} - ORDER BY f.year - OFFSET ${Number(offset) || 0} ROWS - FETCH NEXT ${Number(limit) || 10} ROWS ONLY; - `) - return result + // Get original figures from EMS + const originalFigures = await this.emsDataSource.query(` + SELECT o.[object_id], o.[id_city], o.[id_parent], o.[type], o.[planning], o.[activity], o.[kvr], o.[jur], o.[fuel], o.[boiler_id], f.[figure_type_id], f.[left], f.[top], f.[width], f.[height], f.[angle], f.[points], f.[label_left], f.[label_top], f.[label_angle], f.[label_size], f.[year] FROM New_Gis..figures f + JOIN nGeneral..vObjects o ON o.object_id = f.object_id WHERE o.id_city = ${city_id} AND f.year = ${year} + ORDER BY f.year + OFFSET ${Number(offset) || 0} ROWS + FETCH NEXT ${Number(limit) || 10} ROWS ONLY; + `) + + // Get modified figures from SQLite for the same year + const modifiedFigures = await this.dataSource.query( + `SELECT * FROM figures WHERE year = ?`, + [year] + ) + + // Create a lookup map using object_id + year as key + const modifiedMap = new Map() + modifiedFigures.forEach(fig => { + const key = `${fig.object_id}_${fig.year}` + modifiedMap.set(key, fig) + }) + + // Replace original values with modified ones + const mergedResult = originalFigures.map(original => { + const key = `${original.object_id}_${year}` + const modified = modifiedMap.get(key) + + if (modified) { + return { + ...original, + modified: modified.feature + } + } + + return original + }) + + return mergedResult } async getLines(year: number, city_id: number): Promise { - const result = await this.emsDataSource.query( - ` - SELECT * FROM New_Gis..lines l - JOIN nGeneral..vObjects o ON l.object_id = o.object_id WHERE o.id_city = ${city_id} AND l.year = ${year}; - ` + // Get original lines from EMS + const originalLines = await this.emsDataSource.query(` + SELECT o.[object_id], o.[id_city], o.[id_parent], o.[type], o.[planning], o.[activity], o.[kvr], o.[jur], o.[fuel], o.[boiler_id], + l.[x1], l.[y1], l.[x2], l.[y2], l.[points], l.[label_offset], l.[group_id], l.[show_label], l.[forced_lengths], l.[label_sizes], l.[label_angles], l.[label_positions], l.[year] + FROM New_Gis..lines l + JOIN nGeneral..vObjects o ON l.object_id = o.object_id WHERE o.id_city = ${city_id} AND l.year = ${year}; + `) + + // Get modified lines from SQLite for the same year + const modifiedLines = await this.dataSource.query( + `SELECT * FROM lines WHERE year = ?`, + [year] ) - return result + // Create lookup map with object_id + year + const modifiedMap = new Map() + modifiedLines.forEach(line => { + const key = `${line.object_id}_${line.year}` + modifiedMap.set(key, line) + }) + + // Replace original with modified + const mergedResult = originalLines.map(original => { + const key = `${original.object_id}_${year}` + const modified = modifiedMap.get(key) + + if (modified) { + return { + ...original, + modified: modified.feature + } + } + + return original + }) + + return mergedResult } async getRegionBorders(): Promise { @@ -150,4 +232,63 @@ export class GisService { return result } + + async updateFeaturesBatch(features: any[]) { + let figures: any[] = [] + let lines: any[] = [] + + features.map(feature => { + if (feature.type === 'figure') { + figures.push(feature) + } else if (feature.type === 'line') { + lines.push(feature) + } + }) + + console.log('Figures to update:', figures.length) + console.log('Lines to update:', lines.length) + + // Update figures + if (figures.length > 0) { + const figurePlaceholders = figures.map(() => + `(?, ?, ?)` + ).join(', ') + + const figureParams = figures.flatMap(fig => [ + fig.object_id, + fig.year, // Default year if not provided + JSON.stringify(fig.feature) + ]) + + await this.dataSource.query(` + INSERT OR REPLACE INTO figures + (object_id, year, feature) + VALUES ${figurePlaceholders} + `, figureParams) + } + + // Update lines + if (lines.length > 0) { + const linePlaceholders = lines.map(() => + `(?, ?, ?)` + ).join(', ') + + const lineParams = lines.flatMap(line => [ + line.object_id, + line.year, + JSON.stringify(line.feature) + ]) + + await this.dataSource.query(` + INSERT OR REPLACE INTO lines + (object_id, year, feature) + VALUES ${linePlaceholders} + `, lineParams) + } + + return { + success: true, + message: `Updated ${figures.length} figures and ${lines.length} lines` + } + } } diff --git a/server/src/main.ts b/server/src/main.ts index 63aa087..68462f3 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -2,12 +2,14 @@ import { NestFactory } from '@nestjs/core'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; +import { json } from 'express'; async function bootstrap() { const app = await NestFactory.create(AppModule, { cors: true }); app.enableCors() + app.use(json({ limit: '50mb' })) app.useGlobalPipes(new ValidationPipe({ transform: true })) const config = new DocumentBuilder() .setTitle('Fuel API')