import { BaseLayer } from './base_layer' /** * Family layer showing family member locations * Each member has unique color */ export class FamilyLayer extends BaseLayer { constructor(map, options = {}) { super(map, { id: 'family', ...options }) this.memberColors = {} } getSourceConfig() { return { type: 'geojson', data: this.data || { type: 'FeatureCollection', features: [] } } } getLayerConfigs() { return [ // Member circles { id: this.id, type: 'circle', source: this.sourceId, paint: { 'circle-radius': 10, 'circle-color': ['get', 'color'], 'circle-stroke-width': 2, 'circle-stroke-color': '#ffffff', 'circle-opacity': 0.9 } }, // Member labels { id: `${this.id}-labels`, type: 'symbol', source: this.sourceId, layout: { 'text-field': ['get', 'name'], 'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'], 'text-size': 12, 'text-offset': [0, 1.5], 'text-anchor': 'top' }, paint: { 'text-color': '#111827', 'text-halo-color': '#ffffff', 'text-halo-width': 2 } }, // Pulse animation { id: `${this.id}-pulse`, type: 'circle', source: this.sourceId, paint: { 'circle-radius': [ 'interpolate', ['linear'], ['zoom'], 10, 15, 15, 25 ], 'circle-color': ['get', 'color'], 'circle-opacity': [ 'interpolate', ['linear'], ['get', 'lastUpdate'], Date.now() - 10000, 0, Date.now(), 0.3 ] } } ] } getLayerIds() { return [this.id, `${this.id}-labels`, `${this.id}-pulse`] } /** * Update single family member location * @param {Object} member - { id, name, latitude, longitude, color } */ updateMember(member) { const features = this.data?.features || [] // Find existing or add new const index = features.findIndex(f => f.properties.id === member.id) const feature = { type: 'Feature', geometry: { type: 'Point', coordinates: [member.longitude, member.latitude] }, properties: { id: member.id, name: member.name, color: member.color || this.getMemberColor(member.id), lastUpdate: Date.now() } } if (index >= 0) { features[index] = feature } else { features.push(feature) } this.update({ type: 'FeatureCollection', features }) } /** * Get consistent color for member */ getMemberColor(memberId) { if (!this.memberColors[memberId]) { const colors = [ '#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899' ] const index = Object.keys(this.memberColors).length % colors.length this.memberColors[memberId] = colors[index] } return this.memberColors[memberId] } /** * Remove family member */ removeMember(memberId) { const features = this.data?.features || [] const filtered = features.filter(f => f.properties.id !== memberId) this.update({ type: 'FeatureCollection', features: filtered }) } }