Added apartment views and city of edmonton data import
This commit is contained in:
132
map/app/controllers/externalDataController.js
Normal file
132
map/app/controllers/externalDataController.js
Normal file
@@ -0,0 +1,132 @@
|
||||
const socrataService = require('../services/socrata');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const EDMONTON_PARCEL_ADDRESSES_ID = 'nggt-rwac';
|
||||
|
||||
class ExternalDataController {
|
||||
/**
|
||||
* Fetches parcel addresses from the City of Edmonton open data portal.
|
||||
* Uses a simple SoQL query to get points with valid locations.
|
||||
*/
|
||||
async getEdmontonParcelAddresses(req, res) {
|
||||
try {
|
||||
logger.info('Fetching Edmonton parcel addresses from Socrata API');
|
||||
|
||||
// Get query parameters for filtering and pagination
|
||||
const {
|
||||
bounds,
|
||||
zoom = 10,
|
||||
limit = 2000,
|
||||
offset = 0,
|
||||
neighborhood
|
||||
} = req.query;
|
||||
|
||||
// Build dynamic query based on zoom level and bounds
|
||||
let whereClause = 'location IS NOT NULL';
|
||||
let selectFields = 'house_number, street_name, sub_address, neighbourhood_name, object_type, location, latitude, longitude';
|
||||
|
||||
// If bounds are provided, filter by geographic area
|
||||
if (bounds) {
|
||||
try {
|
||||
const boundsArray = bounds.split(',').map(Number);
|
||||
if (boundsArray.length === 4) {
|
||||
const [south, west, north, east] = boundsArray;
|
||||
whereClause += ` AND latitude BETWEEN ${south} AND ${north} AND longitude BETWEEN ${west} AND ${east}`;
|
||||
logger.info(`Filtering by bounds: ${bounds}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('Invalid bounds parameter:', bounds);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by neighborhood if specified
|
||||
if (neighborhood) {
|
||||
whereClause += ` AND neighbourhood_name = '${neighborhood.toUpperCase()}'`;
|
||||
}
|
||||
|
||||
// Adjust limit based on zoom level - show fewer points when zoomed out
|
||||
const dynamicLimit = Math.min(parseInt(zoom) < 12 ? 500 : parseInt(zoom) < 15 ? 1500 : 2000, parseInt(limit));
|
||||
|
||||
const params = {
|
||||
'$select': selectFields,
|
||||
'$where': whereClause,
|
||||
'$limit': dynamicLimit,
|
||||
'$offset': parseInt(offset),
|
||||
'$order': 'house_number'
|
||||
};
|
||||
|
||||
const data = await socrataService.get(EDMONTON_PARCEL_ADDRESSES_ID, params);
|
||||
|
||||
logger.info(`Successfully fetched ${data.length} Edmonton parcel addresses (zoom: ${zoom}, bounds: ${bounds})`);
|
||||
|
||||
// Group addresses by location to identify multi-unit buildings
|
||||
const locationGroups = new Map();
|
||||
|
||||
data.filter(item => item.location && item.location.coordinates).forEach(item => {
|
||||
const locationKey = `${item.latitude}_${item.longitude}`;
|
||||
const address = `${item.house_number || ''} ${item.street_name || ''}`.trim();
|
||||
|
||||
if (!locationGroups.has(locationKey)) {
|
||||
locationGroups.set(locationKey, {
|
||||
address: address || 'No address',
|
||||
location: item.location,
|
||||
latitude: parseFloat(item.latitude),
|
||||
longitude: parseFloat(item.longitude),
|
||||
neighbourhood_name: item.neighbourhood_name || '',
|
||||
suites: []
|
||||
});
|
||||
}
|
||||
|
||||
locationGroups.get(locationKey).suites.push({
|
||||
suite: item.sub_address || item.suite || '',
|
||||
object_type: item.object_type || 'SUITE',
|
||||
record_id: item.record_id || '',
|
||||
house_number: item.house_number || '',
|
||||
street_name: item.street_name || ''
|
||||
});
|
||||
});
|
||||
|
||||
// Transform grouped data into GeoJSON FeatureCollection
|
||||
const validFeatures = Array.from(locationGroups.values()).map(group => ({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
address: group.address,
|
||||
neighborhood: group.neighbourhood_name,
|
||||
suites: group.suites,
|
||||
suiteCount: group.suites.length,
|
||||
isMultiUnit: group.suites.length > 3,
|
||||
lat: group.latitude,
|
||||
lng: group.longitude
|
||||
},
|
||||
geometry: group.location
|
||||
}));
|
||||
|
||||
const geoJson = {
|
||||
type: 'FeatureCollection',
|
||||
features: validFeatures,
|
||||
metadata: {
|
||||
count: validFeatures.length,
|
||||
zoom: zoom,
|
||||
bounds: bounds,
|
||||
hasMore: validFeatures.length === dynamicLimit // Indicates if there might be more data
|
||||
}
|
||||
};
|
||||
|
||||
logger.info(`Processed ${validFeatures.length} valid features`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: geoJson
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error fetching Edmonton parcel addresses:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch external map data.'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ExternalDataController();
|
||||
Reference in New Issue
Block a user