# OpenLocationCode
An Elixir implementation of Google's Open Location Code (Plus Code) system for encoding and decoding geographic locations.
Plus Codes are short, 10-11 character codes that can be used instead of street addresses. The codes can be generated and decoded offline, and use a reduced character set that minimizes the chance of codes including words.
## Features
- **Encode** latitude/longitude coordinates to Plus Codes
- **Decode** Plus Codes back to coordinate areas
- **Shorten** codes relative to a reference location
- **Recover** full codes from shortened versions
- **Validate** code format and structure
- **Offline operation** - no internet connection required
## Installation
Add `olc` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:olc, "~> 0.1.0"}
]
end
```
## Quick Start
```elixir
# Encode a location (Zurich, Switzerland)
{:ok, code} = OpenLocationCode.encode(47.365590, 8.524997)
# => {:ok, "8FVC9G8F+6X"}
# Decode a Plus Code
{:ok, area} = OpenLocationCode.decode("8FVC9G8F+6X")
# => {:ok, %OpenLocationCode.CodeArea{...}}
# Access the decoded coordinates
area.latitude_center # => 47.36558750000001
area.longitude_center # => 8.524997500000002
```
## Usage
### Encoding Coordinates
Convert latitude and longitude to a Plus Code:
```elixir
# Default precision (10 characters)
{:ok, code} = OpenLocationCode.encode(47.365590, 8.524997)
# => {:ok, "8FVC9G8F+6X"}
# Higher precision (11 characters)
{:ok, code} = OpenLocationCode.encode(47.365590, 8.524997, 11)
# => {:ok, "8FVC9G8F+6XQ"}
# Lower precision (6 characters)
{:ok, code} = OpenLocationCode.encode(47.365590, 8.524997, 6)
# => {:ok, "8FVC00+"}
```
### Decoding Plus Codes
Convert Plus Codes back to geographic areas:
```elixir
{:ok, area} = OpenLocationCode.decode("8FVC9G8F+6X")
# Access bounding box coordinates
area.latitude_lo # => 47.365575
area.longitude_lo # => 8.524975
area.latitude_hi # => 47.3656
area.longitude_hi # => 8.52502
area.latitude_center # => 47.36558750000001
area.longitude_center # => 8.524997500000002
area.code_length # => 10
```
### Shortening Codes
Remove characters from the start of a code using a reference location:
```elixir
# The closer the reference location, the more characters can be removed
{:ok, short_code} = OpenLocationCode.shorten("8FVC9G8F+6X", 47.5, 8.5)
# => {:ok, "9G8F+6X"}
# Very close reference location allows more shortening
{:ok, shorter_code} = OpenLocationCode.shorten("8FVC9G8F+6X", 47.365590, 8.524997)
# => {:ok, "8F+6X"}
```
### Recovering Full Codes
Expand shortened codes back to full codes using a reference location:
```elixir
{:ok, full_code} = OpenLocationCode.recover_nearest("9G8F+6X", 47.4, 8.6)
# => {:ok, "8FVC9G8F+6X"}
{:ok, full_code} = OpenLocationCode.recover_nearest("8F+6X", 47.4, 8.6)
# => {:ok, "8FVC9G8F+6X"}
```
### Validation
Check if codes are valid, short, or full:
```elixir
OpenLocationCode.valid?("8FVC9G8F+6X") # => true
OpenLocationCode.valid?("invalid") # => false
OpenLocationCode.short?("9G8F+6X") # => true
OpenLocationCode.short?("8FVC9G8F+6X") # => false
OpenLocationCode.full?("8FVC9G8F+6X") # => true
OpenLocationCode.full?("9G8F+6X") # => false
```
## Code Areas and Precision
Plus Codes represent rectangular areas, not exact points. The length of the code determines the precision:
| Code Length | Grid Size (at equator) | Example Use Case |
|-------------|------------------------|------------------|
| 6 characters | ~13.9 km × 13.9 km | City/town level |
| 8 characters | ~690 m × 690 m | Neighborhood |
| 10 characters | ~13.5 m × 13.5 m | Building identification |
| 11 characters | ~2.8 m × 3.5 m | Precise locations |
## API Reference
### Core Functions
- `encode(latitude, longitude, code_length \\ 10)` - Encode coordinates to Plus Code
- `decode(code)` - Decode Plus Code to coordinate area
- `shorten(code, latitude, longitude)` - Shorten code relative to location
- `recover_nearest(code, latitude, longitude)` - Recover full code from short code
### Validation Functions
- `valid?(code)` - Check if code format is valid
- `short?(code)` - Check if code is a short code
- `full?(code)` - Check if code is a full code
### Utility Functions
- `clip_latitude(latitude)` - Clip latitude to valid range (-90 to 90)
- `normalize_longitude(longitude)` - Normalize longitude to valid range (-180 to 180)
## Error Handling
Functions return tagged tuples for clear error handling:
```elixir
case OpenLocationCode.decode("invalid") do
{:ok, area} ->
# Process the decoded area
IO.puts("Center: #{area.latitude_center}, #{area.longitude_center}")
{:error, :invalid_code} ->
IO.puts("Invalid code format")
{:error, :full_code_expected} ->
IO.puts("Expected a full code, got a short code")
end
```
### Common Error Types
- `:invalid_code` - Code format is invalid
- `:full_code_expected` - Operation requires a full code
- `:cannot_shorten_padded_codes` - Cannot shorten codes with padding
- `:code_length_too_small` - Code too short to be shortened further
- `:invalid_open_location_code_length` - Invalid code length specified
## Use Cases
### Address Replacement
Plus Codes can serve as addresses in areas without traditional addressing systems:
```elixir
# Generate a Plus Code for a location
{:ok, address_code} = OpenLocationCode.encode(-1.2921, 36.8219) # Nairobi
# Share "6GCRMRFC+" as an address
```
### Location Sharing
Share precise locations without revealing exact coordinates:
```elixir
# Share a shortened code relative to a known landmark
{:ok, short_code} = OpenLocationCode.shorten(full_code, landmark_lat, landmark_lng)
# Share the shorter, more memorable code
```
### Emergency Services
Provide precise location information in areas with poor addressing:
```elixir
# Generate high-precision code for emergency response
{:ok, precise_code} = OpenLocationCode.encode(lat, lng, 11)
# 11-character code provides ~3m precision
```
## Technical Details
- **Character Set**: Uses 20 characters (23456789CFGHJMPQRVWX) avoiding vowels and similar-looking characters
- **Grid System**: Based on a hierarchical grid system with base-20 encoding
- **Precision**: Each additional character increases precision by factor of 20
- **Offline**: All operations work without internet connectivity
- **Global**: Works anywhere on Earth
## Contributing
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Make your changes with tests
4. Run the test suite (`mix test`)
5. Commit your changes (`git commit -m 'Add amazing feature'`)
6. Push to the branch (`git push origin feature/amazing-feature`)
7. Open a Pull Request
## License
This project is licensed under the MIT License - see the LICENSE file for details.
## References
- [Open Location Code Specification](https://github.com/google/open-location-code/blob/main/Documentation/Specification/specification.md)
- [Plus Codes Official Site](https://plus.codes/)
- [Google's Open Location Code Repository](https://github.com/google/open-location-code)
## Acknowledgments
- Based on Google's Open Location Code specification
- Inspired by implementations in other programming languages