# AddressService The `AddressService` is a singleton service providing distance calculations, proximity queries, duplicate detection, formatting and coordinate conversion. ## Accessing the Service ```php // Via the global helper address()->distanceBetween($a, $b); // Via dependency injection use Blax\Addresses\Services\AddressService; public function __construct(private AddressService $addressService) {} // Via the container app(AddressService::class)->nearby($lat, $lng, 10); ``` --- ## Distance Calculation ### `distanceBetween(Address $from, Address $to, string $unit = 'km'): ?float` Calculate the great-circle distance between two addresses using the Haversine formula. ```php $vienna = Address::create(['latitude' => 48.2082, 'longitude' => 16.3738]); $berlin = Address::create(['latitude' => 52.5200, 'longitude' => 13.4050]); $km = address()->distanceBetween($vienna, $berlin); // ~524.2 km $mi = address()->distanceBetween($vienna, $berlin, 'mi'); // ~325.8 mi ``` Returns `null` if either address is missing coordinates. ### `haversine(float $lat1, float $lng1, float $lat2, float $lng2, string $unit = 'km'): float` Calculate distance directly from coordinate pairs — no Address models needed. ```php $distance = address()->haversine(48.2082, 16.3738, 52.5200, 13.4050); // ~524.2 km ``` ### `altitudeDifference(Address $from, Address $to): ?float` Calculate the altitude difference in metres (signed: `to − from`). ```php $valley = Address::create(['altitude' => 200.0, 'latitude' => 0, 'longitude' => 0]); $peak = Address::create(['altitude' => 1800.0, 'latitude' => 0, 'longitude' => 0]); address()->altitudeDifference($valley, $peak); // 1600.0 address()->altitudeDifference($peak, $valley); // -1600.0 ``` Returns `null` if either address is missing altitude data. ### Constants | Constant | Value | Description | |-----------------------------------|--------|---------------------------------| | `AddressService::EARTH_RADIUS_KM` | 6371.0 | Mean Earth radius in kilometres | | `AddressService::EARTH_RADIUS_MI` | 3958.8 | Mean Earth radius in miles | --- ## Proximity Queries ### `nearby(float $latitude, float $longitude, float $radius, string $unit = 'km'): Collection` Find all addresses within a given radius of a coordinate point. Uses a bounding-box pre-filter for performance, then refines with Haversine. Results are ordered by distance (nearest first). ```php // All addresses within 10 km of St. Stephen's Cathedral $nearby = address()->nearby(48.2082, 16.3738, 10); foreach ($nearby as $address) { echo $address->city; // "Vienna" echo $address->distance; // 2.34 (km from centre) } ``` Each returned `Address` has a `->distance` attribute appended with the calculated distance. ### `nearbyAddress(Address $address, float $radius, string $unit = 'km', bool $excludeSelf = true): Collection` Convenience wrapper — find addresses near a given address. ```php $office = Address::create([ 'street' => 'Stephansplatz 1', 'city' => 'Vienna', 'latitude' => 48.2082, 'longitude' => 16.3738, ]); // Find other addresses within 5 km $neighbours = address()->nearbyAddress($office, 5); // Include the reference address itself $all = address()->nearbyAddress($office, 5, 'km', false); ``` Returns an empty collection if the address has no coordinates. ### `closest(float $latitude, float $longitude): ?Address` Get the single closest address to a coordinate point. ```php $nearest = address()->closest(48.2082, 16.3738); echo $nearest->formatted; // "Stephansplatz 1, 1010, Vienna, AT" echo $nearest->distance; // 0.12 (km) ``` Returns `null` if no addresses with coordinates exist. --- ## Bounding Box ### `boundingBox(float $latitude, float $longitude, float $radius, string $unit = 'km'): array` Calculate a latitude/longitude bounding box around a centre point. Useful as a fast pre-filter before computing Haversine distances. ```php $box = address()->boundingBox(48.2082, 16.3738, 10); // 10 km radius // Returns: // [ // 'minLat' => 48.1183..., // 'maxLat' => 48.2981..., // 'minLng' => 16.2395..., // 'maxLng' => 16.5081..., // ] // Use in a query Address::whereBetween('latitude', [$box['minLat'], $box['maxLat']]) ->whereBetween('longitude', [$box['minLng'], $box['maxLng']]) ->get(); ``` --- ## Duplicate Detection & Merging ### `findDuplicates(Address $address): Collection` Find addresses that look like potential duplicates. Matches on `street`, `postal_code`, `city` and `country_code`. ```php $address = Address::create([ 'street' => 'Baker Street 221B', 'postal_code' => 'NW1 6XE', 'city' => 'London', 'country_code' => 'GB', ]); $duplicates = address()->findDuplicates($address); foreach ($duplicates as $dup) { echo "Possible duplicate: #{$dup->id} — {$dup->formatted}"; } ``` ### `merge(Address $target, Address $duplicate): int` Merge a duplicate address into a target. All `AddressLink` rows pointing to the duplicate are reassigned to the target, and the duplicate is soft-deleted. ```php $target = Address::find(1); // the one to keep $duplicate = Address::find(2); // the one to merge away $reassigned = address()->merge($target, $duplicate); echo "Reassigned {$reassigned} links"; $duplicate->trashed(); // true ``` --- ## Query Builders These methods return Eloquent `Builder` instances for further chaining. ### `inCountry(string $countryCode): Builder` ```php $austrianAddresses = address()->inCountry('AT')->get(); $austrianCount = address()->inCountry('AT')->count(); ``` ### `inCity(string $city, ?string $countryCode = null): Builder` ```php $viennaAddresses = address()->inCity('Vienna')->get(); // Disambiguate: Vienna, Austria vs Vienna, Virginia $at = address()->inCity('Vienna', 'AT')->get(); ``` ### `inPostalCode(string $postalCode, ?string $countryCode = null): Builder` ```php $addresses = address()->inPostalCode('1010')->get(); $addresses = address()->inPostalCode('1010', 'AT')->get(); ``` ### `withCoordinates(): Builder` Get all addresses that have latitude and longitude set. ```php $geoAddresses = address()->withCoordinates()->get(); $count = address()->withCoordinates()->count(); ``` --- ## Formatting ### `format(Address $address, string $separator = ', '): string` Build a single-line formatted string from an address. ```php $address = Address::create([ 'street' => '350 Fifth Avenue', 'building' => 'Empire State Building', 'floor' => '32', 'postal_code' => '10118', 'city' => 'New York', 'state' => 'NY', 'country_code' => 'US', ]); echo address()->format($address); // "350 Fifth Avenue, (Empire State Building), Floor 32, 10118, New York, NY, US" echo address()->format($address, ' | '); // "350 Fifth Avenue | (Empire State Building) | Floor 32 | 10118 | New York | NY | US" ``` > **Tip:** The `Address` model also has a `$address->formatted` accessor that produces the same single-line output with `", "` separator. ### `formatMultiline(Address $address): string` Build a multi-line, postal-style formatted string. ```php echo address()->formatMultiline($address); // 350 Fifth Avenue // Empire State Building, Floor 32 // 10118 New York, NY // US ``` Line structure: 1. Street + street_extra 2. Building, floor, room 3. Postal code + city, state 4. County (if set) 5. Country code ### `formatCoordinates(Address $address): ?string` Format coordinates as a human-readable string. ```php $address = Address::create([ 'latitude' => 48.2082, 'longitude' => 16.3738, 'altitude' => 171.0, ]); echo address()->formatCoordinates($address); // "48.2082000°N, 16.3738000°E (alt: 171.00m AMSL)" ``` Returns `null` if no coordinates are set. --- ## Coordinate Conversion ### `dmsToDecimal(int $degrees, int $minutes, float $seconds, string $direction): float` Convert degrees/minutes/seconds (DMS) to decimal degrees. ```php // 48° 12' 29.52" N $lat = address()->dmsToDecimal(48, 12, 29.52, 'N'); // 48.2082 // 16° 22' 25.68" E $lng = address()->dmsToDecimal(16, 22, 25.68, 'E'); // 16.3738 // Southern / Western hemispheres yield negative values $lat = address()->dmsToDecimal(33, 51, 54.0, 'S'); // -33.865 ``` ### `decimalToDms(float $decimal, string $axis = 'lat'): array` Convert decimal degrees to DMS. ```php $dms = address()->decimalToDms(48.2082, 'lat'); // [ // 'degrees' => 48, // 'minutes' => 12, // 'seconds' => 29.52, // 'direction' => 'N', // ] $dms = address()->decimalToDms(-73.9854, 'lng'); // [ // 'degrees' => 73, // 'minutes' => 59, // 'seconds' => 7.44, // 'direction' => 'W', // ] ``` The `$axis` parameter determines the direction letter: - `'lat'` → N (positive) / S (negative) - `'lng'` → E (positive) / W (negative)