Skip to content

Commit

Permalink
refactor: Update VehicleMap component to fetch address from attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
ngocjohn committed Oct 20, 2024
1 parent 2fe9c32 commit 0a03b29
Show file tree
Hide file tree
Showing 9 changed files with 276 additions and 328 deletions.
202 changes: 34 additions & 168 deletions src/components/cards/vic-map-card.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,21 @@
import { LitElement, html, css, TemplateResult, PropertyValues, CSSResultGroup } from 'lit';
import { customElement, state, property } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';

import { HA as HomeAssistant, VehicleCardConfig } from '../../types';
import { MapData } from '../../types';

// Leaflet imports
import L from 'leaflet';
import 'leaflet-providers/leaflet-providers.js';

import mapstyle from '../../css/leaflet.css';
import { VehicleCard } from '../../vehicle-info-card';

interface Address {
streetNumber: string;
streetName: string;
sublocality: string;
city: string;
state: string;
country: string;
postcode: string;
}
import { isEmpty } from '../../utils';

@customElement('vehicle-map')
export class VehicleMap extends LitElement {
@property({ attribute: false }) private hass!: HomeAssistant;
@property({ type: Object }) private config!: VehicleCardConfig;
@property() card!: VehicleCard;

@state() private address: Partial<Address> = {};
@state() private deviceTracker: { lat: number; lon: number } = { lat: 0, lon: 0 };

@state() private adressLoaded: boolean = false;
@state() private locationLoaded: boolean = false;
@property({ attribute: false }) private mapData!: MapData;
@property({ attribute: false }) private card!: VehicleCard;

@state() private map: L.Map | null = null;
@state() private marker: L.Marker | null = null;
Expand Down Expand Up @@ -60,6 +45,11 @@ export class VehicleMap extends LitElement {
width: 100%;
height: 100%;
}
.map-wrapper.loading {
display: flex;
align-items: center;
justify-content: center;
}
.map-overlay {
position: absolute;
top: 0;
Expand Down Expand Up @@ -191,82 +181,37 @@ export class VehicleMap extends LitElement {
];
}

private get apiKey(): string | undefined {
return this.config.google_api_key;
}

private get darkMode(): boolean {
return this.card.isDark;
}

private get mapPopup(): boolean {
return this.config.enable_map_popup;
return this.card.config.enable_map_popup;
}

protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
this.updateCSSVariables();
this._getDeviceTracker();
}

updated(changedProperties: PropertyValues): void {
if (changedProperties.has('darkMode')) {
this.updateCSSVariables();
this.updateMap();
}
}

private _getDeviceTracker(): void {
if (!this.config.device_tracker) return;

this.locationLoaded = false;

const deviceTracker = this.config.device_tracker;
const stateObj = this.hass.states[deviceTracker];

if (!stateObj) return;
const { latitude, longitude } = stateObj.attributes;
this.deviceTracker = { lat: latitude, lon: longitude };

console.log('deviceTracker', this.deviceTracker);

this.locationLoaded = true;

this.getAddress(latitude, longitude);

this.updateComplete.then(() => {
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (changedProperties.has('mapData')) {
this.initMap();
});
}

async getAddress(lat: number, lon: number): Promise<void> {
this.adressLoaded = false;
const address = this.apiKey
? await this.getAddressFromGoggle(lat, lon)
: await this.getAddressFromOpenStreet(lat, lon);

if (address) {
this.address = address;
this.adressLoaded = true;
this.requestUpdate();
}
// console.log('address', this.address);
}

private updateCSSVariables(): void {
if (this.darkMode) {
this.style.setProperty('--vic-map-marker-color', 'var(--accent-color)');
this.style.setProperty('--vic-marker-filter', 'var(--vic-marker-dark-filter)');
this.style.setProperty('--vic-map-tiles-filter', 'var(--vic-map-tiles-dark-filter)');
} else {
this.style.setProperty('--vic-map-marker-color', 'var(--primary-color)');
this.style.setProperty('--vic-marker-filter', 'var(--vic-marker-light-filter)');
this.style.setProperty('--vic-map-tiles-filter', 'var(--vic-map-tiles-light-filter)');
}
private _computeMapStyle() {
return styleMap({
'--vic-map-marker-color': this.darkMode ? 'var(--accent-color)' : 'var(--primary-color)',
'--vic-marker-filter': this.darkMode ? 'var(--vic-marker-dark-filter)' : 'var(--vic-marker-light-filter)',
'--vic-map-tiles-filter': this.darkMode
? 'var(--vic-map-tiles-dark-filter)'
: 'var(--vic-map-tiles-light-filter)',
});
}

initMap(): void {
const { lat, lon } = this.deviceTracker;
const { lat, lon } = this.mapData;
const mapOptions = {
dragging: true,
zoomControl: false,
Expand Down Expand Up @@ -305,8 +250,9 @@ export class VehicleMap extends LitElement {
});
}

this.updateMap();
this.updateCSSVariables();
this.updateComplete.then(() => {
this.updateMap();
});
}

private togglePopup(): void {
Expand All @@ -320,7 +266,7 @@ export class VehicleMap extends LitElement {

private updateMap(): void {
if (!this.map || !this.marker) return;
const { lat, lon } = this.deviceTracker;
const { lat, lon } = this.mapData;
const offset: [number, number] = this.calculateLatLngOffset(this.map, lat, lon, this.map.getSize().x / 5, 3);
this.map.setView(offset, this.zoom);
this.marker.setLatLng([lat, lon]);
Expand All @@ -343,9 +289,9 @@ export class VehicleMap extends LitElement {
}

render(): TemplateResult {
if (!this.locationLoaded) return html``;
if (!this.mapData) return html`<div class="map-wrapper loading"><span class="loader"></span></div>`;
return html`
<div class="map-wrapper">
<div class="map-wrapper" style=${this._computeMapStyle()}>
<div id="map"></div>
<div class="map-overlay"></div>
<div class="reset-button" @click=${this.updateMap}>
Expand All @@ -356,103 +302,23 @@ export class VehicleMap extends LitElement {
`;
}

private _renderAddress() {
if (!this.adressLoaded) {
private _renderAddress(): TemplateResult {
if (!this.mapData.address || isEmpty(this.mapData.address)) {
return html`<div class="address" style="left: 10%;"><span class="loader"></span></div>`;
}
const address = this.mapData?.address || {};
return html`
<div class="address">
<div class="address-line">
<ha-icon icon="mdi:map-marker"></ha-icon>
<div>
<span>${this.address.streetNumber} ${this.address.streetName}</span><br /><span
<span>${address.streetNumber} ${address.streetName}</span><br /><span
style="text-transform: uppercase; opacity: 0.8; letter-spacing: 1px"
>${!this.address.sublocality ? this.address.city : this.address.sublocality}</span
>${!address.sublocality ? address.city : address.sublocality}</span
>
</div>
</div>
</div>
`;
}

private async getAddressFromOpenStreet(lat: number, lon: number): Promise<null | Partial<Address>> {
const url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=jsonv2`;
try {
const response = await fetch(url);
const data = await response.json();

if (response.ok) {
// Extract address components from the response
const address = {
streetNumber: data.address.house_number || '', // Retrieve street number
streetName: data.address.road || '',
sublocality: data.address.suburb || data.address.village || '',
city: data.address.city || data.address.town || '',
state: data.address.state || data.address.county || '',
country: data.address.country || '',
postcode: data.address.postcode || '',
};

return address;
} else {
throw new Error('Failed to fetch address OpenStreetMap');
}
} catch (error) {
// console.error('Error fetching address:', error);
return null;
}
}

private async getAddressFromGoggle(lat: number, lon: number): Promise<null | Partial<Address>> {
const apiKey = this.apiKey; // Replace with your API key
const url = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lon}&key=${apiKey}`;

try {
const response = await fetch(url);
const data = await response.json();

if (data.status === 'OK') {
const addressComponents = data.results[0].address_components;
let streetNumber = '';
let streetName = '';
let sublocality = '';
let city = '';

addressComponents.forEach((component) => {
if (component.types.includes('street_number')) {
streetNumber = component.long_name;
}
if (component.types.includes('route')) {
streetName = component.long_name;
}
if (component.types.includes('sublocality')) {
sublocality = component.short_name;
}

if (component.types.includes('locality')) {
city = component.long_name;
}
// Sometimes city might be under 'administrative_area_level_2' or 'administrative_area_level_1'
if (!city && component.types.includes('administrative_area_level_2')) {
city = component.short_name;
}
if (!city && component.types.includes('administrative_area_level_1')) {
city = component.short_name;
}
});

return {
streetNumber,
streetName,
sublocality,
city,
};
} else {
throw new Error('No results found');
}
} catch (error) {
console.error('Error fetching address:', error);
return null;
}
}
}
2 changes: 1 addition & 1 deletion src/components/cards/vic-vehicle-buttons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import swipercss from '../../css/swiper-bundle.css';
@customElement('vehicle-buttons')
export class VehicleButtons extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Object }) component!: VehicleCard;
@property({ attribute: false }) private component!: VehicleCard;
@property({ type: Object }) _config!: VehicleCardConfig;
@property({ type: Object }) _buttons!: ButtonCardEntity;

Expand Down
11 changes: 4 additions & 7 deletions src/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ const latestRelease: { version: string; hacs: boolean; updated: boolean } = {
@customElement('vehicle-info-card-editor')
export class VehicleCardEditor extends LitElement implements LovelaceCardEditor {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public _config!: VehicleCardConfig;
@property() private baseCardTypes: CardTypeConfig[] = [];
@property({ attribute: false }) public _config!: VehicleCardConfig;
@property({ attribute: false }) private baseCardTypes: CardTypeConfig[] = [];

@property({ attribute: false }) public lovelace?: LovelaceConfig;
@state() private _btnPreview: boolean = false;
Expand Down Expand Up @@ -893,10 +893,7 @@ export class VehicleCardEditor extends LitElement implements LovelaceCardEditor
<ha-icon icon="mdi:pencil" slot="graphic"></ha-icon>
Edit
</mwc-list-item>
<mwc-list-item @click=${this._hideCustomButton(button)} .graphic=${'icon'}>
<ha-icon icon=${eyeIcon} slot="graphic"></ha-icon>
${this.localize(`editor.buttonConfig.${hideShowText}`)}
</mwc-list-item>
${addedCard
? html`
<mwc-list-item
Expand All @@ -914,7 +911,7 @@ export class VehicleCardEditor extends LitElement implements LovelaceCardEditor
? html` <div class="confirm-delete">
<span>${this.localize('editor.buttonConfig.deleteConfirm')}</span>
<ha-button @click=${this._removeCustomCard(type)}><ha-icon icon="mdi:check"></ha-button>
<ha-button @click=${() => (this._confirmDeleteType = null)}><ha-icon icon="mdi:close"></button>
<ha-button @click=${() => (this._confirmDeleteType = null)}><ha-icon icon="mdi:close"> </ha-icon></ha-button>
</div>`
: nothing}
</div>
Expand Down
24 changes: 24 additions & 0 deletions src/types/card-types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { LovelaceCardConfig } from 'custom-card-helpers';
import { CustomButtonEntity } from './config';
export type HEADER_ACTION = 'next' | 'prev' | 'close';

export type CardTypeConfig = {
Expand Down Expand Up @@ -43,3 +45,25 @@ export type CustomButton = {
notify: boolean;
state: string;
};

export interface Address {
streetNumber: string;
streetName: string;
sublocality: string;
city: string;
state: string;
country: string;
postcode: string;
}

export interface MapData {
lat: number;
lon: number;
address?: Partial<Address>;
popUpCard?: LovelaceCardConfig[];
}

export interface PreviewCard {
cardPreview?: LovelaceCardConfig[];
buttonPreview?: Partial<CustomButtonEntity>;
}
Loading

0 comments on commit 0a03b29

Please sign in to comment.