pwshPlaces.psm1

# This is a locally sourced Imports file for local development.
# It can be imported by the psm1 in local development to add script level variables.
# It will merged in the build process. This is for local development only.

#region script variables
# $script:resourcePath = "$PSScriptRoot\Resources"

$script:googleMapsBaseURI = 'https://maps.googleapis.com/maps/api/'
$script:bingMapsBaseURI = 'https://dev.virtualearth.net/REST/'

#endregion

#region parameter enums

enum ccTLD {
    ac #Ascension Island
    ad #Andorra
    ae #United Arab Emirate
    af #Afghanistan
    ag #Antigua and Barbuda
    ai #Anguilla
    al #Albania
    am #Armenia
    ao #Angola
    aq #Antarctica
    ar #Argentina
    as #American Samoa
    at #Austria
    au #Australia
    aw #Aruba
    ax #Åland Islands
    az #Azerbaijan
    ba #Bosnia and Herzegov
    bb #Barbados
    bd #Bangladesh
    be #Belgium
    bf #Burkina Faso
    bg #Bulgaria
    bh #Bahrain
    bi #Burundi
    bj #Benin
    bm #Bermuda
    bn #Brunei
    bo #Bolivia
    br #Brazil
    bs #Bahamas
    bt #Bhutan
    bv #Bouvet Island
    bw #Botswana
    by #Belarus
    bz #Belize
    ca #Canada
    cc #Cocos (Keeling) Isl
    cd #Democratic Republic
    cf #Central African Rep
    cg #Republic of the Con
    ch #Switzerland
    ci #Ivory Coast
    ck #Cook Islands
    cl #Chile
    cm #Cameroon
    cn #China
    co #Colombia
    cr #Costa Rica
    cu #Cuba
    cv #Cape Verde
    cw #Curaçao
    cx #Christmas Island
    cy #Cyprus
    cz #Czech Republic
    de #Germany
    dj #Djibouti
    dk #Denmark
    dm #Dominica
    do #Dominican Republic
    dz #Algeria
    ec #Ecuador
    ee #Estonia
    eg #Egypt
    er #Eritrea
    es #Spain
    et #Ethiopia
    eu #European Union
    fi #Finland
    fj #Fiji
    fk #Falkland Islands
    fm #Micronesia
    fo #Faroe Islands
    fr #France
    ga #Gabon
    gb #United Kingdom
    gd #Grenada
    ge #Georgia
    gf #French Guiana
    gg #Guernsey
    gh #Ghana
    gi #Gibraltar
    gl #Greenland
    gm #Gambia
    gn #Guinea
    gp #Guadeloupe
    gq #Equatorial Guinea
    gr #Greece
    gs #South Georgia
    gt #Guatemala
    gu #Guam
    gw #Guinea-Bissau
    gy #Guyana
    hk #Hong Kong
    hm #Heard Island and Mc
    hn #Honduras
    hr #Croatia
    ht #Haiti
    hu #Hungary
    id #Indonesia
    ie #Ireland
    il #Israel
    im #Isle of Man
    in #India
    io #British Indian Ocea
    iq #Iraq
    ir #Iran
    is #Iceland
    it #Italy
    je #Jersey
    jm #Jamaica
    jo #Jordan
    jp #Japan
    ke #Kenya
    kg #Kyrgyzstan
    kh #Cambodia
    ki #Kiribati
    km #Comoros
    kn #Saint Kitts and Nev
    kp #North Korea
    kr #South Korea
    kw #Kuwait
    ky #Cayman Islands
    kz #Kazakhstan
    la #Laos
    lb #Lebanon
    lc #Saint Lucia
    li #Liechtenstein
    lk #Sri Lanka
    lr #Liberia
    ls #Lesotho
    lt #Lithuania
    lu #Luxembourg
    lv #Latvia
    ly #Libya
    ma #Morocco
    mc #Monaco
    md #Moldova
    me #Montenegro
    mg #Madagascar
    mh #Marshall Islands
    mk #North Macedonia
    ml #Mali
    mm #Myanmar
    mn #Mongolia
    mo #Macao
    mp #Northern Mariana Is
    mq #Martinique
    mr #Mauritania
    ms #Montserrat
    mt #Malta
    mu #Mauritius
    mv #Maldives
    mw #Malawi
    mx #Mexico
    my #Malaysia
    mz #Mozambique
    na #Namibia
    nc #New Caledonia
    ne #Niger
    nf #Norfolk Island
    ng #Nigeria
    ni #Nicaragua
    nl #Netherlands
    no #Norway
    np #Nepal
    nr #Nauru
    nu #Niue
    nz #New Zealand
    om #Oman
    pa #Panama
    pe #Peru
    pf #French Polynesia
    pg #Papua New Guinea
    ph #Philippines
    pk #Pakistan
    pl #Poland
    pm #Saint Pierre and Mi
    pn #Pitcairn Islands
    pr #Puerto Rico
    ps #Palestine
    pt #Portugal
    pw #Palau
    py #Paraguay
    qa #Qatar
    re #Réunion
    ro #Romania
    rs #Serbia
    ru #Russia
    rw #Rwanda
    sa #Saudi Arabia
    sb #Solomon Islands
    sc #Seychelles
    sd #Sudan
    se #Sweden
    sg #Singapore
    sh #Saint Helena
    si #Slovenia
    sj #Svalbard and Jan Ma
    sk #Slovakia
    sl #Sierra Leone
    sm #San Marino
    sn #Senegal
    so #Somalia
    sr #Suriname
    ss #South Sudan
    st #Sao Tome and Princi
    su #Soviet Union
    sv #El Salvador
    sx #Sint Maarten
    sy #Syria
    sz #Eswatini
    tc #Turks and Caicos Is
    td #Chad
    tf #French Southern Ter
    tg #Togo
    th #Thailand
    tj #Tajikistan
    tk #Tokelau
    tl #East Timor
    tm #Turkmenistan
    tn #Tunisia
    to #Tonga
    tr #Turkey
    tt #Trinidad and Tobago
    tv #Tuvalu
    tw #Taiwan
    tz #Tanzania
    ua #Ukraine
    ug #Uganda
    uk #United Kingdom
    us #United States
    uy #Uruguay
    uz #Uzbekistan
    va #Vatican
    vc #Saint Vincent and t
    ve #Venezuela
    vg #British Virgin Isla
    vi #U.S. Virgin Islands
    vn #Vietnam
    vu #Vanuatu
    wf #Wallis and Futuna
    ws #Samoa
    ye #Yemen
    yt #Mayotte
    za #South Africa
    zm #Zambia
    zw #Zimbabwe
} #enum_ccTLD

#region GMaps enums

# https://developers.google.com/maps/faq#languagesupport
enum languages {
    af      #Afrikaans
    sq      #Albanian
    am      #Amharic
    ar      #Arabic
    hy      #Armenian
    az      #Azerbaijani
    eu      #Basque
    be      #Belarusian
    bn      #Bengali
    bs      #Bosnian
    bg      #Bulgarian
    my      #Burmese
    ca      #Catalan
    zh      #Chinese
    # zh-CN #Chinese (Simplified)
    # zh-HK #Chinese (Hong Kong)
    # zh-TW #Chinese (Traditional)
    hr      #Croatian
    cs      #Czech
    da      #Danish
    nl      #Dutch
    en      #English
    # en-AU #English (Australian)
    # en-GB #English (Great Britain)
    et      #Estonian
    fa      #Farsi
    fi      #Finnish
    fil     #Filipino
    fr      #French
    # fr-CA #French (Canada)
    ka      #Georgian
    de      #German
    el      #Greek
    iw      #Hebrew
    hi      #Hindi
    hu      #Hungarian
    is      #Icelandic
    id      #Indonesian
    it      #Italian
    ja      #Japanese
    kn      #Kannada
    kk      #Kazakh
    km      #Khmer
    ko      #Korean
    ky      #Kyrgyz
    lo      #Lao
    lv      #Latvian
    lt      #Lithuanian
    mk      #Macedonian
    ms      #Malay
    ml      #Malayalam
    mr      #Marathi
    mn      #Mongolian
    ne      #Nepali
    no      #Norwegian
    pl      #Polish
    pt      #Portuguese
    # pt-BR #Portuguese (Brazil)
    # pt-PT #Portuguese (Portugal)
    pa      #Punjabi
    ro      #Romanian
    ru      #Russian
    sr      #Serbian
    # si #Sinhalese
    sk      #Slovak
    # sl #Slovenian
    es      #Spanish
    # es-419 #Spanish (Latin America)
    sw      #Swahili
    ta      #Tamil
    te      #Telugu
    th      #Thai
    uk      #Ukrainian
    ur      #Urdu
    uz      #Uzbek
    vi      #Vietnamese
    zu      #Zulu
} #enum_languages

# https://developers.google.com/maps/documentation/places/web-service/supported_types#table1
enum placeTypes {
    accounting
    airport
    amusement_park
    aquarium
    art_gallery
    atm
    bakery
    bank
    bar
    beauty_salon
    bicycle_store
    book_store
    bowling_alley
    bus_station
    cafe
    campground
    car_dealer
    car_rental
    car_repair
    car_wash
    casino
    cemetery
    church
    city_hall
    clothing_store
    convenience_store
    courthouse
    dentist
    department_store
    doctor
    drugstore
    electrician
    electronics_store
    embassy
    fire_station
    florist
    funeral_home
    furniture_store
    gas_station
    gym
    hair_care
    hardware_store
    hindu_temple
    home_goods_store
    hospital
    insurance_agency
    jewelry_store
    laundry
    lawyer
    library
    light_rail_station
    liquor_store
    local_government_office
    locksmith
    lodging
    meal_delivery
    meal_takeaway
    mosque
    movie_rental
    movie_theater
    moving_company
    museum
    night_club
    painter
    park
    parking
    pet_store
    pharmacy
    physiotherapist
    plumber
    police
    post_office
    primary_school
    real_estate_agency
    restaurant
    roofing_contractor
    rv_park
    school
    secondary_school
    shoe_store
    shopping_mall
    spa
    stadium
    storage
    store
    subway_station
    supermarket
    synagogue
    taxi_stand
    tourist_attraction
    train_station
    transit_station
    travel_agency
    university
    veterinary_care
    zoo
} #enum_placeTypes

#endregion

#region bing enums

enum cultureCodes {
    af              #Afrikaans
    am              #Amharic
    #ar-sa #Arabic (Saudi Arabia)
    as              #Assamese
    #az-Latn #Azerbaijani (Latin)
    be              #Belarusian
    bg              #Bulgarian
    #bn-BD #Bangla (Bangladesh)
    #bn-IN #Bangla (India)
    bs              #Bosnian (Latin)
    ca              #Catalan Spanish
    #ca-ES-valencia #Valencian
    cs              #Czech
    cy              #Welsh
    da              #Danish
    de              #German (Germany)
    #de-de #German (Germany)**
    el              #Greek
    #en-GB #English (United Kingdom)
    #en-US #English (United States)**
    es              #Spanish (Spain)
    #es-ES #Spanish (Spain)**
    #es-US #Spanish (United States)1
    #es-MX #Spanish (Mexico)1
    et              #Estonian
    eu              #Basque
    fa              #Persian
    fi              #Finnish
    #fil-Latn #Filipino
    fr              #French (France)
    #fr-FR #French (France)**
    #fr-CA #French (Canada)2
    ga              #Irish
    #gd-Latn #Scottish Gaelic
    #gl #Galician
    #gu #Gujarati
    #ha-Latn #Hausa (Latin)
    he              #Hebrew
    hi              #Hindi
    hr              #Croatian
    hu              #Hungarian
    hy              #Armenian
    id              #Indonesian
    #ig-Latn #Igbo
    is              #Icelandic
    it              #Italian (Italy)
    #it-it #Italian (Italy)**
    ja              #Japanese
    ka              #Georgian
    kk              #Kazakh
    km              #Khmer
    kn              #Kannada
    ko              #Korean
    kok             #Konkani
    #ku-Arab #Central Kurdish
    #ky-Cyrl #Kyrgyz
    lb              #Luxembourgish
    lt              #Lithuanian
    lv              #Latvian
    #mi-Latn #Maori
    mk              #Macedonian
    ml              #Malayalam
    #mn-Cyrl #Mongolian (Cyrillic)
    mr              #Marathi
    ms              #Malay (Malaysia)
    mt              #Maltese
    nb              #Norwegian (Bokmål)
    ne              #Nepali (Nepal)
    nl              #Dutch (Netherlands)
    #nl-BE #Dutch (Netherlands)**
    nn              #Norwegian (Nynorsk)
    nso             #Sesotho sa Leboa
    or              #Odia
    pa              #Punjabi (Gurmukhi)
    #pa-Arab #Punjabi (Arabic)
    pl              #Polish
    #prs-Arab #Dari
    #pt-BR #Portuguese (Brazil)
    #pt-PT #Portuguese (Portugal)
    #qut-Latn #K’iche’
    quz             #Quechua (Peru)
    ro              #Romanian (Romania)
    ru              #Russian
    rw              #Kinyarwanda
    #sd-Arab #Sindhi (Arabic)
    #si #Sinhala
    sk              #Slovak
    #sl #Slovenian
    sq              #Albanian
    #sr-Cyrl-BA #Serbian (Cyrillic, Bosnia and Herzegovina)
    #sr-Cyrl-RS #Serbian (Cyrillic, Serbia)
    #sr-Latn-RS #Serbian (Latin, Serbia)
    #sv #Swedish (Sweden)
    sw              #Kiswahili
    ta              #Tamil
    te              #Telugu
    #tg-Cyrl #Tajik (Cyrillic)
    th              #Thai
    ti              #Tigrinya
    #tk-Latn #Turkmen (Latin)
    tn              #Setswana
    tr              #Turkish
    #tt-Cyrl #Tatar (Cyrillic)
    #ug-Arab #Uyghur
    uk              #Ukrainian
    ur              #Urdu
    #uz-Latn #Uzbek (Latin)
    vi              #Vietnamese
    wo              #Wolof
    xh              #isiXhosa
    #yo-Latn #Yoruba
    #zh-Hans #Chinese (Simplified)
    #zh-Hant #Chinese (Traditional)
    zu              #isiZulu
} #enum_cultureCodes

# https://learn.microsoft.com/en-us/bingmaps/rest-services/common-parameters-and-types/type-identifiers/
enum typeIdentifier {
    AmusementParks                  # Amusement parks.
    AntiqueStores                   # For retail stores that sell antiques.
    Attractions                     # Miscellaneous attractions that don't belong to another type.
    BanksAndCreditUnions
    Bars                            # For establishments that serve primarily alcoholic drinks such as beer, wine, liquor, and cocktails for consumption by the patrons on certain premises.
    BarsGrillsAndPubs               # A casual restaurant that serves substantial amount of food and alcoholic beverages on the menu.
    BelgianRestaurants              # Restaurants that primarily serve cuisine from Belgium.
    Bookstores                      # For retail stores that primarily sell books, and may also include newspapers, magazines, maps.
    BreweriesAndBrewPubs            # For businesses or entities which brew and process beer on the premises, typically also serving their products in-house to the public.
    BritishRestaurants              # A restaurant that primarily serves cuisine from the British cultural traditions, including English, Welsh, Cornish and Scottish.
    BuffetRestaurants               # A restaurant where food is presented buffet-style and patrons can serve themselves.
    CDAndRecordStores               # Includes music stores that primarily sell music via CDs, Records, Albums, Cassette Tapes. May also sell music accessories for those devices.
    CafeRestaurants                 # This casual eating and drinking place offers significant food options, prepared to order, and seating at the premise.
    CaribbeanRestaurants            # A restaurant that primarily serves cuisine from the Caribbean traditions.
    Carnivals                       # Carnival attractions.
    Casinos                         # Casinos and other gambling establishments.
    ChildrensClothingStores         # For retail stores that primarily sell clothing for children (including infants).
    ChineseRestaurants              # A restaurant that primarily serves cuisine from the Chinese cultural traditions.
    CigarAndTobaccoShops            # For retail stores that sell tobacco and tobacco accessories.
    CocktailLounges                 # For bars whose focus is serving and selling alcoholic mixed drinks.
    CoffeeAndTea                    # For establishments that primarily serve coffee and/or tea.
    ComicBookStores                 # For retail stores that focus sales on comic books, comic magazines, and other comic accessories.
    Delicatessens                   # In the United States, a delicatessen store, or deli, is a type of business that is both a grocery store and serves food.
    DeliveryService                 # This type includes business which offer the option to deliver prepared dishes to different locations like home or work.
    DepartmentStores                # For retailers that primarily sell a wide range of consumer goods such as clothing, housewares, furniture and appliances, toys, cosmetics.
    Diners                          # Diners are characterized by offering a wide range of foods, mostly American, a casual atmosphere, a counter, and late operating hours.
    DiscountStores                  # For retail stores that primarily sell products at lower prices than standard retail stores. Discount stores may or may not have various departments offering a wide variety of goods.
    Donuts                          # For places that primarily serve donuts.
    FastFood                        # For businesses characterized by their quickness, cheap food, and minimal service.
    FleaMarketsAndBazaars           # Where individuals are able to rent a space to display and sell their goods that are usually used/old at a lower price.
    FrenchRestaurants               # A restaurant that primarily serves cuisine from the French cultural traditions.
    FrozenYogurt                    # For entities that primarily serve frozen yogurt such as FroYo and Frogurt.
    FurnitureStores                 # For retail stores that sell objects (whether new or used) intended to support various human activities such as sitting and sleeping.
    GermanRestaurants               # Restaurants that primarily serve cuisine from Germany.
    GreekRestaurants                # Restaurants that primarily serve cuisine from the Greek cultural traditions.
    Grocers                         # Retail entities that provide food products to consumers, including corner stores.
    Grocery                         # Retail entities that provide food products to consumers.
    HawaiianRestaurants             # A restaurant that primarily serves cuisine from Hawaiian traditions.
    HomeImprovementStores           # For retail stores that primarily sell hardware for home improvements such as power tools, electrical supplies and other related items.
    Hospitals
    HotelsAndMotels
    HungarianRestaurants            # Restaurants that primarily serve food from Hungarian cuisine.
    IceCreamAndFrozenDesserts       # For businesses that primarily serve ice cream and other frozen desserts, like cheese cake.
    IndianRestaurants               # Restaurants that primarily serve cuisine from Indian cultural traditions.
    ItalianRestaurants              # Restaurants that primarily serve cuisine from Italian cultural traditions.
    JapaneseRestaurants             # Restaurants that primarily serve cuisine from Japanese cultural traditions.
    JewelryAndWatchesStores         # For retail stores which sell jewelry and watches, as well as business that purchase gold and silver from the buyer ("cash for gold").
    Juices                          # For businesses offering juices and smoothies.
    KitchenwareStores               # For retail stores that primarily sell tableware, cookware and bakeware.
    KoreanRestaurants               # Restaurants that primarily serve cuisine from Korean cultural traditions.
    LandmarksAndHistoricalSites     # Man-made landmarks and historical sites.
    LiquorStores                    # For retail stores that primarily sell liquor, wine, beer and spirits.
    MallsAndShoppingCenters         # For large complexes with various retail shops and interconnecting walkways allowing shoppers to have a complete shopping experience.
    MensClothingStores              # For retail stores that primarily sell clothing specifically designed for men to wear
    MexicanRestaurants              # Restaurants that primarily serve cuisine from Mexican cultural traditions.
    MiddleEasternRestaurants        # Restaurants that primarily serve cuisine from Middle Eastern cultural traditions.
    MiniatureGolfCourses            # Miniature golf courses.
    MovieTheaters                   # Movie theaters and houses.
    Museums                         # Museums and art galleries.
    MusicStores                     # For retail shops which sell music related equipment or offer music related services.
    OutletStores                    # For retail shops which sell music related equipment or offer music related services.
    Parking
    Parks                           # Natural parks and gardens.
    PetShops                        # for retail stores that primarily sell pets.
    PetSupplyStores                 # For retail stores that primarily sell pet supplies.
    Pizza                           # Restaurants that primarily serve pizza, including pizza deliveries.
    PolishRestaurants               # Restaurants that primarily serve cuisine from Polish cultural traditions.
    PortugueseRestaurants           # Restaurants that primarily serve cuisine from Portuguese cultural traditions.
    Pretzels                        # For businesses, places, kiosks, stands, or carts that primarily sell pretzels.
    Restaurants                     # Business entities with the primary purpose of serving prepared-to-order food to the public.
    RussianAndUkrainianRestaurants  # Restaurants that primarily serve cuisine from Russian and/or Ukrainian cultural traditions.
    Sandwiches                      # Restaurants that primarily serve sandwiches.
    SchoolAndOfficeSupplyStores     # Includes all office-related products. Pens, pencils, whiteboard markers, binders, mailing/shipping supplies, and basic office machines (fax, media projectors, etc.). Also includes retail-specific supplies, like shopping bags, signs, and cash handling products. All forms and types of calendars, breakroom and office cleaning supplies, and in-person meeting supplies.
    SeafoodRestaurants              # A restaurant that primarily serves seafood, including freshwater fish.
    ShoeStores                      # For retail stores that primarily sell footwear.
    SightseeingTours                # Sight-seeing tours.
    SpanishRestaurants              # Restaurants that primarily serve cuisine from Spanish cultural traditions.
    SportingGoodsStores             # For retail stores that primarily sell sport and fitness equipment.
    SportsBars                      # Bars with a primary focus on viewing live sporting and entertainment events.
    SteakHouseRestaurants           # Restaurants that primarily serve beef steaks.
    Supermarkets                    # Large, warehouse-like retail entities that provide food products to consumers.
    SushiRestaurants                # Restaurants that primarily service sushi, including both cooked and raw seafood.
    TakeAway                        # A food establishment that offers the option to purchase prepared dishes for the purpose of being eaten elsewhere.
    Taverns                         # Similar to bars, but with additional seating; typically these are businesses which were once residences.
    ThaiRestaurants                 # Restaurants that primarily serve cuisine from Thai cultural traditions.
    TouristInformation              # Tourist information booths and visitor centers.
    ToyAndGameStores                # For retail stores that primarily sell toys and games for all ages, including electronic toys, and educational game systems.
    TurkishRestaurants              # Restaurants that primarily serve cuisine from Turkish cultural traditions.
    VegetarianAndVeganRestaurants   # Restaurant that primarily serve vegetarian and/or vegan cuisine.
    VietnameseRestaurants           # Restaurants that primarily serve cuisine from Vietnamese cultural traditions.
    VitaminAndSupplementStores      # For retail stores that primarily sell vitamins and supplements.
    WomensClothingStores            # For retail stores that primarily sell women's clothing.
    Zoos                            # Zoos and aquariums.
} #enum_typeIdentifier

#endregion

#endregion


<#
.SYNOPSIS
    Formats GeoCode object with easier to understand properties while leaving original properties intact
.DESCRIPTION
    Changes GeoCode object to custom PSType and creates additional properties that contains information most likely to be viewed.
.EXAMPLE
    $results.resourceSets.resources | Format-BingGeoCode
 
    Formats GeoCode object results with a set of easier to understand properties
.PARAMETER Results
    GeoCode API results
.OUTPUTS
    Bing.GeoCode
.NOTES
    Author: Jake Morrison - @jakemorrison - https://www.techthoughts.info/
 
    Based on code from: https://github.com/Kreloc/PoshGMaps
.COMPONENT
    pwshPlaces
#>

function Format-BingGeoCode {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = 'GeoCode API results',
            ValueFromPipeline = $true)]
        $Results
    )
    begin {
        Write-Verbose ('Starting {0}' -f $myinvocation.mycommand)
    } #begin
    process {
        Write-Debug -Message ($Results | Out-String)

        $Results.PSTypeNames.Insert(0, 'Bing.GeoCode')

        Write-Debug -Message ($Results.PSTypeNames | Out-String)

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.GeoCode'
            MemberType = 'ScriptProperty'
            MemberName = 'FormattedAddress'
            Value      = { $this.address.formattedAddress }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.GeoCode'
            MemberType = 'ScriptProperty'
            MemberName = 'Street'
            Value      = { $this.address.addressLine }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.GeoCode'
            MemberType = 'ScriptProperty'
            MemberName = 'City'
            Value      = { $this.address.locality }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.GeoCode'
            MemberType = 'ScriptProperty'
            MemberName = 'Country'
            Value      = { $this.address.countryRegion }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.GeoCode'
            MemberType = 'ScriptProperty'
            MemberName = 'PostalCode'
            Value      = { $this.address.postalCode }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.GeoCode'
            MemberType = 'ScriptProperty'
            MemberName = 'Latitude'
            Value      = { $this.point.coordinates[0] }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.GeoCode'
            MemberType = 'ScriptProperty'
            MemberName = 'Longitude'
            Value      = { $this.point.coordinates[1] }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        # set a default display of the above properties, all other properties are still there just not displayed
        $updateTypeDataSplat = @{
            TypeName                  = 'Bing.GeoCode'
            DefaultDisplayPropertySet = 'name', 'FormattedAddress', 'Street', 'City', 'Country', 'PostalCode', 'Latitude', 'Longitude', 'entityType'
            DefaultKeyPropertySet     = 'name'
            Force                     = $true
        }
        Update-TypeData @updateTypeDataSplat

        $Results
    } #process
    end {
        Write-Verbose ('Ending {0}' -f $myinvocation.mycommand)
    } #end
} #Format-BingGeoCode


<#
.SYNOPSIS
    Formats Place object with easier to understand properties while leaving original properties intact
.DESCRIPTION
    Changes Place object to custom PSType and creates additional properties that contains information most likely to be viewed.
.EXAMPLE
    $results.resourceSets.resources | Format-BingPlace
 
    Formats GeoCode object results with a set of easier to understand properties
.PARAMETER Results
    Place API results
.OUTPUTS
    Bing.Place
.NOTES
    Author: Jake Morrison - @jakemorrison - https://www.techthoughts.info/
 
    Based on code from: https://github.com/Kreloc/PoshGMaps
.COMPONENT
    pwshPlaces
#>

function Format-BingPlace {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = 'GeoCode API results',
            ValueFromPipeline = $true)]
        $Results
    )
    begin {
        Write-Verbose ('Starting {0}' -f $myinvocation.mycommand)
    } #begin
    process {
        Write-Debug -Message ($Results | Out-String)

        $Results.PSTypeNames.Insert(0, 'Bing.Place')

        Write-Debug -Message ($Results.PSTypeNames | Out-String)

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.Place'
            MemberType = 'ScriptProperty'
            MemberName = 'FormattedAddress'
            Value      = { $this.address.formattedAddress }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.Place'
            MemberType = 'ScriptProperty'
            MemberName = 'Latitude'
            Value      = { $this.point.coordinates[0] }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.Place'
            MemberType = 'ScriptProperty'
            MemberName = 'Longitude'
            Value      = { $this.point.coordinates[1] }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        # set a default display of the above properties, all other properties are still there just not displayed
        $updateTypeDataSplat = @{
            TypeName                  = 'Bing.Place'
            DefaultDisplayPropertySet = 'name', 'FormattedAddress', 'PhoneNumber', 'Website', 'Latitude', 'Longitude', 'entityType'
            DefaultKeyPropertySet     = 'name'
            Force                     = $true
        }
        Update-TypeData @updateTypeDataSplat

        $Results
    } #process
    end {
        Write-Verbose ('Ending {0}' -f $myinvocation.mycommand)
    } #end
} #Format-BingPlace


<#
.SYNOPSIS
    Formats TimeZone object with easier to understand properties while leaving original properties intact
.DESCRIPTION
    Changes TimeZone object to custom PSType and creates additional properties that contains information most likely to be viewed.
.EXAMPLE
    $results.resourceSets.resources.timeZone | Format-BingTimeZone
 
    Formats TimeZone object results with a set of easier to understand properties
.PARAMETER Results
    TimeZone API results
.OUTPUTS
    Bing.TimeZone
.NOTES
    Author: Jake Morrison - @jakemorrison - https://www.techthoughts.info/
 
    Based on code from: https://github.com/Kreloc/PoshGMaps
.COMPONENT
    pwshPlaces
#>

function Format-BingTimeZone {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = 'TimeZone API results',
            ValueFromPipeline = $true)]
        $Results
    )
    begin {
        Write-Verbose ('Starting {0}' -f $myinvocation.mycommand)
    } #begin
    process {
        Write-Debug -Message ($Results | Out-String)

        $Results.PSTypeNames.Insert(0, 'Bing.TimeZone')

        Write-Debug -Message ($Results.PSTypeNames | Out-String)

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.TimeZone'
            MemberType = 'ScriptProperty'
            MemberName = 'TimeZoneName'
            Value      = { $this.genericName }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.TimeZone'
            MemberType = 'ScriptProperty'
            MemberName = 'TimeZoneShort'
            Value      = { $this.abbreviation }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.TimeZone'
            MemberType = 'ScriptProperty'
            MemberName = 'TimeZoneID'
            Value      = { $this.ianaTimeZoneId }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.TimeZone'
            MemberType = 'ScriptProperty'
            MemberName = 'LocalTime'
            Value      = { $this.convertedTime.localTime }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.TimeZone'
            MemberType = 'ScriptProperty'
            MemberName = 'UTCOffSetDST'
            Value      = { $this.convertedTime.utcOffsetWithDst }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.TimeZone'
            MemberType = 'ScriptProperty'
            MemberName = 'TimeZoneCurrentName'
            Value      = { $this.convertedTime.timeZoneDisplayName }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'Bing.TimeZone'
            MemberType = 'ScriptProperty'
            MemberName = 'TimeZoneCurrentShort'
            Value      = { $this.convertedTime.timeZoneDisplayAbbr }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        # set a default display of the above properties, all other properties are still there just not displayed
        $updateTypeDataSplat = @{
            TypeName                  = 'Bing.TimeZone'
            DefaultDisplayPropertySet = 'TimeZoneName', 'TimeZoneShort', 'UTCOffSet', 'TimeZoneID', 'LocalTime', 'TimeZoneCurrentName', 'TimeZoneCurrentShort', 'UTCOffSetDST', 'dstRule'
            DefaultKeyPropertySet     = 'TimeZoneName'
            Force                     = $true
        }
        Update-TypeData @updateTypeDataSplat

        $Results
    } #process
    end {
        Write-Verbose ('Ending {0}' -f $myinvocation.mycommand)
    } #end
} #Format-BingTimeZone


<#
.SYNOPSIS
    Formats GeoCode object with easier to understand properties while leaving original properties intact
.DESCRIPTION
    Changes GeoCode object to custom PSType and creates additional properties that contains information most likely to be viewed.
.EXAMPLE
    $results.result | Format-GMapGeoCode
 
    Formats GeoCode object results with a set of easier to understand properties
.PARAMETER Results
    GeoCode API results
.OUTPUTS
    GMap.GeoCode
.NOTES
    Author: Jake Morrison - @jakemorrison - https://www.techthoughts.info/
 
    Based on code from: https://github.com/Kreloc/PoshGMaps
.COMPONENT
    pwshPlaces
#>

function Format-GMapGeoCode {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = 'GeoCode API results',
            ValueFromPipeline = $true)]
        $Results
    )
    begin {
        Write-Verbose ('Starting {0}' -f $myinvocation.mycommand)
    } #begin
    process {
        Write-Debug -Message ($Results | Out-String)

        $Results.PSTypeNames.Insert(0, 'GMap.GeoCode')

        Write-Debug -Message ($Results.PSTypeNames | Out-String)

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.GeoCode'
            MemberType = 'ScriptProperty'
            MemberName = 'StreetNumber'
            Value      = { $(($this.address_components | Where-Object { $_.types -contains "street_number" }).long_name) }
            Force      = $true
        }
        Write-Verbose ($updateTypeDataSplat | Out-String)
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.GeoCode'
            MemberType = 'ScriptProperty'
            MemberName = 'Street'
            Value      = { $($this.address_components | Where-Object { $_.types -contains "route" }).long_name }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.GeoCode'
            MemberType = 'ScriptProperty'
            MemberName = 'City'
            Value      = { $($this.address_components | Where-Object { $_.types -contains "locality" }).long_name }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.GeoCode'
            MemberType = 'ScriptProperty'
            MemberName = 'Country'
            Value      = { $($this.address_components | Where-Object { $_.types -contains "country" }).long_name }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.GeoCode'
            MemberType = 'ScriptProperty'
            MemberName = 'PostalCode'
            Value      = { $($this.address_components | Where-Object { $_.types -contains "postal_code" }).long_name }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.GeoCode'
            MemberType = 'ScriptProperty'
            MemberName = 'Latitude'
            Value      = { $this.geometry.location.lat }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.GeoCode'
            MemberType = 'ScriptProperty'
            MemberName = 'Longitude'
            Value      = { $this.geometry.location.lng }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        # set a default display of the above properties, all other properties are still there just not displayed
        $updateTypeDataSplat = @{
            TypeName                  = 'GMap.GeoCode'
            DefaultDisplayPropertySet = 'place_id', 'formatted_address', 'StreetNumber', 'Street', 'City', 'Country', 'PostalCode', 'Latitude', 'Longitude', 'types'
            DefaultKeyPropertySet     = 'place_id'
            Force                     = $true
        }
        Update-TypeData @updateTypeDataSplat

        $Results
    } #process
    end {
        Write-Verbose ('Ending {0}' -f $myinvocation.mycommand)
    } #end
} #Format-GMapGeoCode


<#
.SYNOPSIS
    Formats Nearby Place object with easier to understand properties while leaving original properties intact
.DESCRIPTION
    Changes Nearby Place object to custom PSType and creates additional properties that contains information most likely to be viewed.
.EXAMPLE
    $results.results | Format-GMapNearbyPlace
 
    Formats Nearby Place object results with a set of easier to understand properties
.PARAMETER Results
    Nearby Place API results
.OUTPUTS
    GMap.NearbyPlace
.NOTES
    Author: Jake Morrison - @jakemorrison - https://www.techthoughts.info/
 
    Based on code from: https://github.com/Kreloc/PoshGMaps
.COMPONENT
    pwshPlaces
#>

function Format-GMapNearbyPlace {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = 'Place API results',
            ValueFromPipeline = $true)]
        $Results
    )
    begin {
        Write-Verbose ('Starting {0}' -f $myinvocation.mycommand)
    } #begin
    process {
        Write-Debug -Message ($Results | Out-String)

        $Results.PSTypeNames.Insert(0, 'GMap.NearbyPlace')

        Write-Debug -Message ($Results.PSTypeNames | Out-String)

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.NearbyPlace'
            MemberType = 'ScriptProperty'
            MemberName = 'Latitude'
            Value      = { $this.geometry.location.lat }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.NearbyPlace'
            MemberType = 'ScriptProperty'
            MemberName = 'Longitude'
            Value      = { $this.geometry.location.lng }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.NearbyPlace'
            MemberType = 'ScriptProperty'
            MemberName = 'Address'
            Value      = { $this.vicinity }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.NearbyPlace'
            MemberType = 'ScriptProperty'
            MemberName = 'Open'
            Value      = { $this.opening_hours.open_now }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        # set a default display of the above properties, all other properties are still there just not displayed
        $updateTypeDataSplat = @{
            TypeName                  = 'GMap.NearbyPlace'
            DefaultDisplayPropertySet = 'place_id', 'name', 'Address', 'Latitude', 'Longitude', 'types', 'rating', 'user_ratings_total', 'price_level', 'Open'
            DefaultKeyPropertySet     = 'place_id'
            Force                     = $true
        }
        Update-TypeData @updateTypeDataSplat

        $Results
    } #process
    end {
        Write-Verbose ('Ending {0}' -f $myinvocation.mycommand)
    } #end
} #Format-GMapNearbyPlace


<#
.SYNOPSIS
    Formats Place object with easier to understand properties while leaving original properties intact
.DESCRIPTION
    Changes Place object to custom PSType and creates additional properties that contains information most likely to be viewed.
.EXAMPLE
    $results.candidates | Format-GMapPlace
 
    Formats Place object results with a set of easier to understand properties
.PARAMETER Results
    Place API results
.OUTPUTS
    GMap.Place
.NOTES
    Author: Jake Morrison - @jakemorrison - https://www.techthoughts.info/
 
    Based on code from: https://github.com/Kreloc/PoshGMaps
.COMPONENT
    pwshPlaces
#>

function Format-GMapPlace {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = 'Place API results',
            ValueFromPipeline = $true)]
        $Results
    )
    begin {
        Write-Verbose ('Starting {0}' -f $myinvocation.mycommand)
    } #begin
    process {
        Write-Debug -Message ($Results | Out-String)

        $Results.PSTypeNames.Insert(0, 'GMap.Place')

        Write-Debug -Message ($Results.PSTypeNames | Out-String)

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.Place'
            MemberType = 'ScriptProperty'
            MemberName = 'Latitude'
            Value      = { $this.geometry.location.lat }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.Place'
            MemberType = 'ScriptProperty'
            MemberName = 'Longitude'
            Value      = { $this.geometry.location.lng }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.Place'
            MemberType = 'ScriptProperty'
            MemberName = 'Address'
            Value      = { $this.formatted_address }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        # if ($Contact) {
        $updateTypeDataSplat = @{
            TypeName   = 'GMap.Place'
            MemberType = 'ScriptProperty'
            MemberName = 'Open'
            Value      = { $this.opening_hours.open_now }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat
        # }

        # set a default display of the above properties, all other properties are still there just not displayed
        $updateTypeDataSplat = @{
            TypeName                  = 'GMap.Place'
            DefaultDisplayPropertySet = 'place_id', 'name', 'Address', 'Open', 'rating', 'user_ratings_total', 'price_level', 'Latitude', 'Longitude', 'types'
            DefaultKeyPropertySet     = 'place_id'
            Force                     = $true
        }
        Update-TypeData @updateTypeDataSplat

        $Results
    } #process
    end {
        Write-Verbose ('Ending {0}' -f $myinvocation.mycommand)
    } #end
} #Format-GMapPlace


<#
.SYNOPSIS
    Formats Detailed Place object with easier to understand properties while leaving original properties intact
.DESCRIPTION
    Changes Detailed Place object to custom PSType and creates additional properties that contains information most likely to be viewed.
.EXAMPLE
    $results.results | Format-GMapPlaceDetail
 
    Formats Detailed Place object results with a set of easier to understand properties
.PARAMETER Results
    Detailed Place API results
.OUTPUTS
    GMap.PlaceDetail
.NOTES
    Author: Jake Morrison - @jakemorrison - https://www.techthoughts.info/
 
    Based on code from: https://github.com/Kreloc/PoshGMaps
.COMPONENT
    pwshPlaces
#>

function Format-GMapPlaceDetail {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = 'Place API results',
            ValueFromPipeline = $true)]
        $Results
    )
    begin {
        Write-Verbose ('Starting {0}' -f $myinvocation.mycommand)
    } #begin
    process {
        Write-Debug -Message ($Results | Out-String)

        $Results.PSTypeNames.Insert(0, 'GMap.PlaceDetail')

        Write-Debug -Message ($Results.PSTypeNames | Out-String)

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.PlaceDetail'
            MemberType = 'ScriptProperty'
            MemberName = 'Latitude'
            Value      = { $this.geometry.location.lat }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.PlaceDetail'
            MemberType = 'ScriptProperty'
            MemberName = 'Longitude'
            Value      = { $this.geometry.location.lng }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.PlaceDetail'
            MemberType = 'ScriptProperty'
            MemberName = 'Address'
            Value      = { $this.formatted_address }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.PlaceDetail'
            MemberType = 'ScriptProperty'
            MemberName = 'Phone'
            Value      = { $this.formatted_phone_number }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.PlaceDetail'
            MemberType = 'ScriptProperty'
            MemberName = 'Open'
            Value      = { $this.opening_hours.open_now }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.PlaceDetail'
            MemberType = 'ScriptProperty'
            MemberName = 'OpenHours'
            Value      = { $this.opening_hours.weekday_text }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.PlaceDetail'
            MemberType = 'ScriptProperty'
            MemberName = 'GoogleMapsURL'
            Value      = { $this.url }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        # set a default display of the above properties, all other properties are still there just not displayed
        $updateTypeDataSplat = @{
            TypeName                  = 'GMap.PlaceDetail'
            DefaultDisplayPropertySet = 'place_id', 'name', 'website', 'Address', 'Phone', 'Open', 'OpenHours', 'GoogleMapsURL', 'rating', 'user_ratings_total', 'price_level', 'Latitude', 'Longitude', 'types'
            DefaultKeyPropertySet     = 'place_id'
            Force                     = $true
        }
        Update-TypeData @updateTypeDataSplat

        $Results
    } #process
    end {
        Write-Verbose ('Ending {0}' -f $myinvocation.mycommand)
    } #end
} #Format-GMapPlaceDetail


<#
.SYNOPSIS
    Formats Text Place object with easier to understand properties while leaving original properties intact
.DESCRIPTION
    Changes Text Place object to custom PSType and creates additional properties that contains information most likely to be viewed.
.EXAMPLE
    $results.results | Format-GMapPlaceText
 
    Formats Text Place object results with a set of easier to understand properties
.PARAMETER Results
    Nearby Place API results
.OUTPUTS
    GMap.PlaceText
.NOTES
    Author: Jake Morrison - @jakemorrison - https://www.techthoughts.info/
 
    Based on code from: https://github.com/Kreloc/PoshGMaps
.COMPONENT
    pwshPlaces
#>

function Format-GMapPlaceText {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            HelpMessage = 'Place API results',
            ValueFromPipeline = $true)]
        $Results
    )
    begin {
        Write-Verbose ('Starting {0}' -f $myinvocation.mycommand)
    } #begin
    process {
        Write-Debug -Message ($Results | Out-String)

        $Results.PSTypeNames.Insert(0, 'GMap.PlaceText')

        Write-Debug -Message ($Results.PSTypeNames | Out-String)

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.PlaceText'
            MemberType = 'ScriptProperty'
            MemberName = 'Latitude'
            Value      = { $this.geometry.location.lat }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.PlaceText'
            MemberType = 'ScriptProperty'
            MemberName = 'Longitude'
            Value      = { $this.geometry.location.lng }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.PlaceText'
            MemberType = 'ScriptProperty'
            MemberName = 'Address'
            Value      = { $this.formatted_address }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        $updateTypeDataSplat = @{
            TypeName   = 'GMap.PlaceText'
            MemberType = 'ScriptProperty'
            MemberName = 'Open'
            Value      = { $this.opening_hours.open_now }
            Force      = $true
        }
        Update-TypeData @updateTypeDataSplat

        # set a default display of the above properties, all other properties are still there just not displayed
        $updateTypeDataSplat = @{
            TypeName                  = 'GMap.PlaceText'
            DefaultDisplayPropertySet = 'place_id', 'name', 'Address', 'Latitude', 'Longitude', 'types', 'rating', 'user_ratings_total', 'price_level', 'Open'
            DefaultKeyPropertySet     = 'place_id'
            Force                     = $true
        }
        Update-TypeData @updateTypeDataSplat

        $Results
    } #process
    end {
        Write-Verbose ('Ending {0}' -f $myinvocation.mycommand)
    } #end
} #Format-GMapPlaceText


<#
.EXTERNALHELP pwshPlaces-help.xml
#>

function Find-BingPlace {
    [CmdletBinding(
        PositionalBinding = $false,
        DefaultParameterSetName = 'textquery')]
    param (
        [Parameter(Mandatory = $true,
            ParameterSetName = 'textquery',
            HelpMessage = 'A string that contains information about a location, such as an address or landmark name')]
        [Parameter(ParameterSetName = 'Point', Mandatory = $false)]
        [Parameter(ParameterSetName = 'Circle', Mandatory = $false)]
        [Parameter(ParameterSetName = 'Rectangle', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$Query,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Point',
            HelpMessage = 'Location bias by point lat')]
        [ValidateNotNullOrEmpty()]
        [string]$PointLatitude,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Point',
            HelpMessage = 'Location bias by point long')]
        [ValidateNotNullOrEmpty()]
        [string]$PointLongitude,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Circle',
            HelpMessage = 'Location bias circular lat')]
        [ValidateNotNullOrEmpty()]
        [string]$CircleLatitude,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Circle',
            HelpMessage = 'Location bias circular long')]
        [ValidateNotNullOrEmpty()]
        [string]$CircleLongitude,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Circle',
            HelpMessage = 'Location bias circular radius')]
        [ValidateRange(0, 5000)]
        [string]$CircleRadius,

        # A string specifying two lat/lng pairs in decimal degrees, representing the south/west and north/east points of a rectangle.
        [Parameter(Mandatory = $true,
            ParameterSetName = 'Rectangle',
            HelpMessage = 'Location bias rectangle south lat')]
        [ValidateNotNullOrEmpty()]
        [string]$SouthLatitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Rectangle',
            HelpMessage = 'Location bias rectangle west long')]
        [ValidateNotNullOrEmpty()]
        [string]$WestLongitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Rectangle',
            HelpMessage = 'Location bias rectangle north lat')]
        [ValidateNotNullOrEmpty()]
        [string]$NorthLatitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Rectangle',
            HelpMessage = 'Location bias rectangle east long')]
        [ValidateNotNullOrEmpty()]
        [string]$EastLongitude,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The region code, specified as a ccTLD')]
        [ccTLD]$RegionBias,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The language in which to return results')]
        [languages]$Language,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Specifies the maximum number of locations to return in the response')]
        [ValidateRange(1, 20)]
        [int]$MaxResults,

        [Parameter(Mandatory = $true,
            HelpMessage = 'Bing Maps API Key')]
        [ValidateNotNullOrEmpty()]
        [string]$BingMapsAPIKey
    )

    Write-Debug -Message ($PSCmdlet.ParameterSetName)

    $uri = '{0}{1}' -f $bingMapsBaseURI, 'v1/LocalSearch?output=json'
    Write-Debug -Message ('Base function URI: {0}' -f $uri)

    $fQuery = '&query={0}' -f [uri]::EscapeDataString($Query)
    $uri += $fQuery

    switch ($PSCmdlet.ParameterSetName) {
        'Point' {
            Write-Debug -Message 'Point specified'
            $combinedPoint = '{0},{1}' -f $PointLatitude, $PointLongitude
            $fLocationBias = '&userLocation={0}' -f [uri]::EscapeDataString($combinedPoint)
            $uri += $fLocationBias
        } #point
        'Circle' {
            Write-Debug -Message 'Circle specified'
            $combinedCircle = '{0},{1},{2}' -f $CircleLatitude, $CircleLongitude, $CircleRadius
            $fLocationBias = '&userCircularMapView={0}' -f [uri]::EscapeDataString($combinedCircle)
            $uri += $fLocationBias
        } #circle
        'Rectangle' {
            Write-Debug -Message 'Rectangle specified'
            $combinedRectangle = '{0},{1},{2},{3}' -f $SouthLatitude, $WestLongitude, $NorthLatitude, $EastLongitude
            $fLocationBias = '&userMapView={0}' -f [uri]::EscapeDataString($combinedRectangle)
            $uri += $fLocationBias
        } #rectangle
    } #switch_ParameterSetName

    if ($RegionBias) {
        Write-Debug -Message ('RegionBias: {0}' -f $RegionBias)
        $fRegion = '&userRegion={0}' -f $RegionBias
        $uri += $fRegion
    }
    if ($MaxResults) {
        Write-Debug -Message ('MaxResults: {0}' -f $MaxResults)
        $fMaxResults = '&maxResults={0}' -f $MaxResults
        $uri += $fMaxResults
    }
    if ($Language) {
        Write-Debug -Message ('Language: {0}' -f $Language)
        $fLanguage = '&culture={0}' -f $Language
        $uri += $fLanguage
    }

    Write-Verbose -Message ('Querying Bing API: {0}' -f $uri)

    Write-Debug -Message 'Adding API key'
    $fAPIKey = '&key={0}' -f $BingMapsAPIKey
    $uri += $fAPIKey

    $invokeRestMethodSplat = @{
        Uri         = $uri
        ErrorAction = 'Stop'
    }
    try {
        $results = Invoke-RestMethod @invokeRestMethodSplat
    }
    catch {
        throw $_
    }

    if ($results.statusDescription -ne 'OK') {
        Write-Warning -Message 'Did not get a successful return from Bing Location API endpoint'
        $finalResults = $results
    }
    elseif (-not ($results.resourceSets.estimatedTotal -ge 1)) {
        Write-Warning -Message 'No results returned from query'
        $finalResults = $results
    }
    else {
        $finalResults = ($results.resourceSets.resources | Format-BingPlace)
    }

    return $finalResults

} #Find-BingPlace



<#
.EXTERNALHELP pwshPlaces-help.xml
#>

function Find-BingTimeZone {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            ParameterSetName = 'textquery',
            HelpMessage = 'A string that contains information about a location, such as an address or landmark name')]
        [ValidateNotNullOrEmpty()]
        [string]$Query,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Point',
            HelpMessage = 'Location bias by point lat')]
        [ValidateNotNullOrEmpty()]
        [string]$PointLatitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Point',
            HelpMessage = 'Location bias by point long')]
        [ValidateNotNullOrEmpty()]
        [string]$PointLongitude,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The region code, specified as a ccTLD')]
        [ccTLD]$RegionBias,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The language in which to return results')]
        [languages]$Language,

        [Parameter(Mandatory = $true,
            HelpMessage = 'Bing Maps API Key')]
        [ValidateNotNullOrEmpty()]
        [string]$BingMapsAPIKey,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Include DST rules')]
        [switch]$IncludeDSTRules
    )

    Write-Debug -Message ($PSCmdlet.ParameterSetName)

    switch ($PSCmdlet.ParameterSetName) {
        'textquery' {
            Write-Debug -Message 'Query specified'

            $uri = '{0}{1}' -f $bingMapsBaseURI, 'v1/TimeZone?output=json'
            Write-Debug -Message ('Base function URI: {0}' -f $uri)

            $fQuery = '&query={0}' -f [uri]::EscapeDataString($Query)
            $uri += $fQuery
        } #point
        'Point' {
            Write-Debug -Message 'Point specified'

            Write-Debug -Message 'Location specified'
            $latLong = 'v1/TimeZone/{0},{1}?output=json' -f $PointLatitude, $PointLongitude
            $uri = '{0}{1}' -f $bingMapsBaseURI, $latLong
            Write-Debug -Message ('Base function URI: {0}' -f $uri)
        } #point
    } #switch_ParameterSetName

    if ($RegionBias) {
        Write-Debug -Message ('RegionBias: {0}' -f $RegionBias)
        $fRegion = '&userRegion={0}' -f $RegionBias
        $uri += $fRegion
    }
    if ($Language) {
        Write-Debug -Message ('Language: {0}' -f $Language)
        $fLanguage = '&culture={0}' -f $Language
        $uri += $fLanguage
    }
    if ($IncludeDSTRules) {
        Write-Debug -Message 'IncludeDSTRules specified'
        $fIncludeDSTRules = '&includeDstRules={0}' -f $IncludeDSTRules
        $uri += $fIncludeDSTRules
    }

    Write-Verbose -Message ('Querying Bing API: {0}' -f $uri)

    Write-Debug -Message 'Adding API key'
    $fAPIKey = '&key={0}' -f $BingMapsAPIKey
    $uri += $fAPIKey

    $invokeRestMethodSplat = @{
        Uri         = $uri
        ErrorAction = 'Stop'
    }
    try {
        $results = Invoke-RestMethod @invokeRestMethodSplat
    }
    catch {
        throw $_
    }

    if ($results.statusDescription -ne 'OK') {
        Write-Warning -Message 'Did not get a successful return from Bing Location API endpoint'
        $finalResults = $results
    }
    elseif (-not ($results.resourceSets.estimatedTotal -ge 1)) {
        Write-Warning -Message 'No results returned from query'
        $finalResults = $results
    }
    else {
        if ($PSCmdlet.ParameterSetName -eq 'Point') {
            $finalResults = ($results.resourceSets.resources.timeZone | Format-BingTimeZone)
        }
        else {
            $finalResults = ($results.resourceSets.resources.timeZoneAtLocation.timeZone | Format-BingTimeZone)
        }
    }

    return $finalResults

} #Find-BingTimeZone



<#
.EXTERNALHELP pwshPlaces-help.xml
#>

function Find-GMapPlace {
    [CmdletBinding(
        PositionalBinding = $false,
        DefaultParameterSetName = 'textquery')]
    param (
        # 'Phone numbers must be in international format (prefixed by a plus sign ("+"), followed by the country code, then the phone number itself)')]
        [Parameter(Mandatory = $true,
            ParameterSetName = 'textquery',
            HelpMessage = 'Text input that identifies the search target, such as a name, address, or phone number.')]
        [Parameter(ParameterSetName = 'Point', Mandatory = $false)]
        [Parameter(ParameterSetName = 'Circle', Mandatory = $false)]
        [Parameter(ParameterSetName = 'Rectangle', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$Query,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Point',
            HelpMessage = 'Location bias by point lat')]
        [ValidateNotNullOrEmpty()]
        [string]$PointLatitude,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Point',
            HelpMessage = 'Location bias by point long')]
        [ValidateNotNullOrEmpty()]
        [string]$PointLongitude,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Circle',
            HelpMessage = 'Location bias circular lat')]
        [ValidateNotNullOrEmpty()]
        [string]$CircleLatitude,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Circle',
            HelpMessage = 'Location bias circular long')]
        [ValidateNotNullOrEmpty()]
        [string]$CircleLongitude,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Circle',
            HelpMessage = 'Location bias circular radius')]
        [ValidateNotNullOrEmpty()]
        [string]$CircleRadius,

        # A string specifying two lat/lng pairs in decimal degrees, representing the south/west and north/east points of a rectangle.
        [Parameter(Mandatory = $true,
            ParameterSetName = 'Rectangle',
            HelpMessage = 'Location bias rectangle south lat')]
        [ValidateNotNullOrEmpty()]
        [string]$SouthLatitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Rectangle',
            HelpMessage = 'Location bias rectangle west long')]
        [ValidateNotNullOrEmpty()]
        [string]$WestLongitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Rectangle',
            HelpMessage = 'Location bias rectangle north lat')]
        [ValidateNotNullOrEmpty()]
        [string]$NorthLatitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Rectangle',
            HelpMessage = 'Location bias rectangle east long')]
        [ValidateNotNullOrEmpty()]
        [string]$EastLongitude,

        [Parameter(Mandatory = $false,
            HelpMessage = 'return additional contact information')]
        [switch]$Contact,

        [Parameter(Mandatory = $false,
            HelpMessage = 'return additional atmosphere information')]
        [switch]$Atmosphere,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The language in which to return results')]
        [languages]$Language,

        [Parameter(Mandatory = $true,
            HelpMessage = 'Google API Key')]
        [ValidateNotNullOrEmpty()]
        [string]$GoogleAPIKey
    )

    <#
        API Notes:
        Required parameters
            input
                Text input that identifies the search target, such as a name, address, or phone number. The input must be a string.
            inputtype
                The type of input. This can be one of either textquery or phonenumber.
                    Phone numbers must be in international format (prefixed by a plus sign ("+"), followed by the country code, then the phone number itself)
        Optional parameters
            fields
                Billing Categories
                    Basic - no charge
                    Contact
                    Atmosphere
            language
            locationbias
                Prefer results in a specified area, by specifying either a radius plus lat/lng, or two lat/lng pairs representing the points of a rectangle. If this parameter is not specified, the API uses IP address biasing by default.
 
        IP bias: Instructs the API to use IP address biasing. Pass the string ipbias (this option has no additional parameters).
        Point: A single lat/lng coordinate. Use the following format: point:lat,lng.
        Circular: A string specifying radius in meters, plus lat/lng in decimal degrees. Use the following format: circle:radius@lat,lng.
        Rectangular: A string specifying two lat/lng pairs in decimal degrees
    #>


    Write-Debug -Message ($PSCmdlet.ParameterSetName)

    $uri = '{0}{1}' -f $googleMapsBaseURI, 'place/findplacefromtext/json?'
    Write-Debug -Message ('Base function URI: {0}' -f $uri)

    $fQuery = 'input={0}' -f [uri]::EscapeDataString($Query)
    $uri += $fQuery

    if ($Query -match '^\+\d+$') {
        Write-Debug -Message 'Phone number input type'
        $fInputType = '&inputtype=phonenumber'
    }
    else {
        $fInputType = '&inputtype=textquery'
    }
    $uri += $fInputType

    switch ($PSCmdlet.ParameterSetName) {
        'Point' {
            Write-Debug -Message 'Point specified'
            $combinedPoint = ':{0},{1}' -f $PointLatitude, $PointLongitude
            $fLocationBias = '&locationbias=point{0}' -f [uri]::EscapeDataString($combinedPoint)
            $uri += $fLocationBias
        } #point
        'Circle' {
            Write-Debug -Message 'Circle specified'
            $combinedCircle = ':{0}@{1},{2}' -f $CircleRadius, $CircleLatitude, $CircleLongitude
            $fLocationBias = '&locationbias=circle{0}' -f [uri]::EscapeDataString($combinedCircle)
            $uri += $fLocationBias
        } #circle
        'Rectangle' {
            Write-Debug -Message 'Rectangle specified'
            $combinedRectangle = ':{0},{1}|{2},{3}' -f $SouthLatitude, $WestLongitude, $NorthLatitude, $EastLongitude
            $fLocationBias = '&locationbias=rectangle{0}' -f [uri]::EscapeDataString($combinedRectangle)
            $uri += $fLocationBias
        } #rectangle
    } #switch_ParameterSetName

    if ($Language) {
        Write-Debug -Message ('Language: {0}' -f $Language)
        $fLanguage = '&language={0}' -f $Language
        $uri += $fLanguage
    }

    $fFields = '&fields='
    $basicFields = 'business_status,formatted_address,geometry,icon,icon_mask_base_uri,icon_background_color,name,photo,place_id,plus_code,type'
    $fFields += [uri]::EscapeDataString($basicFields)
    if ($Contact) {
        $contactFields = ',opening_hours'
        $fFields += [uri]::EscapeDataString($contactFields)
    }
    if ($Atmosphere) {
        $atmosphereFields = ',price_level,rating,user_ratings_total'
        $fFields += [uri]::EscapeDataString($atmosphereFields)
    }
    $uri += $fFields

    Write-Verbose -Message ('Querying Google API: {0}' -f $uri)

    Write-Debug -Message 'Adding API key'
    $fAPIKey = '&key={0}' -f $GoogleAPIKey
    $uri += $fAPIKey
    Write-Debug -Message ('Final URI: {0}' -f $uri)

    $invokeRestMethodSplat = @{
        Uri         = $uri
        ErrorAction = 'Stop'
    }
    try {
        $results = Invoke-RestMethod @invokeRestMethodSplat
    }
    catch {
        throw $_
    }

    if ($results.status -ne 'OK') {
        Write-Warning -Message 'Did not get a successful return from Google Geocode API endpoint'
        $finalResults = $results
    }
    else {
        $finalResults = ($results.candidates | Format-GMapPlace)
    }

    return $finalResults

} #Find-GMapPlace



<#
.EXTERNALHELP pwshPlaces-help.xml
#>

function Get-GMapPlaceDetail {
    [CmdletBinding(
        PositionalBinding = $false,
        DefaultParameterSetName = 'textquery')]
    param (
        [Parameter(Mandatory = $true,
            ParameterSetName = 'textquery',
            HelpMessage = 'The unique identifier of a place in Google Maps')]
        [Parameter(ParameterSetName = 'atmosphereDetail', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$PlaceID,

        [Parameter(Mandatory = $false,
            HelpMessage = 'return additional contact information')]
        [switch]$Contact,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'atmosphereDetail',
            HelpMessage = 'return additional atmosphere information')]
        [switch]$Atmosphere,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'atmosphereDetail',
            HelpMessage = 'The sorting method to use when returning reviews')]
        [ValidateSet('MostRelevant', 'Newest')]
        [string]$ReviewSort = 'MostRelevant',

        [Parameter(Mandatory = $false,
            HelpMessage = 'The language in which to return results')]
        [languages]$Language,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The region code, specified as a ccTLD')]
        [ccTLD]$RegionBias,

        [Parameter(Mandatory = $true,
            HelpMessage = 'Google API Key')]
        [ValidateNotNullOrEmpty()]
        [string]$GoogleAPIKey
    )

    <#
        API Notes:
        Required parameters
            place_id
                A textual identifier that uniquely identifies a place
        Optional parameters
            fields
                Billing Categories
                    Basic - no charge
                        address_component, adr_address, business_status, formatted_address, geometry, icon, icon_mask_base_uri, icon_background_color, name, permanently_closed (deprecated), photo, place_id, plus_code, type, url, utc_offset, vicinity
                    Contact
                        formatted_phone_number, international_phone_number, opening_hours, website
                    Atmosphere
                        price_level, rating, review, user_ratings_total.
                            reviews_sort
                                The sorting method to use when returning reviews. Can be set to most_relevant (default) or newest.
            language
            region
    #>


    Write-Debug -Message ($PSCmdlet.ParameterSetName)

    $uri = '{0}{1}' -f $googleMapsBaseURI, 'place/details/json?'
    Write-Debug -Message ('Base function URI: {0}' -f $uri)

    $fPlaceID = 'place_id={0}' -f $PlaceID
    $uri += $fPlaceID

    if ($Language) {
        Write-Debug -Message ('Language: {0}' -f $Language)
        $fLanguage = '&language={0}' -f $Language
        $uri += $fLanguage
    }

    $fFields = '&fields='
    $basicFields = 'address_component,adr_address,business_status,formatted_address,geometry,icon,icon_mask_base_uri,icon_background_color,name,photo,place_id,plus_code,type,url,utc_offset,vicinity'
    $fFields += [uri]::EscapeDataString($basicFields)
    if ($Contact) {
        $contactFields = ',formatted_phone_number,international_phone_number,opening_hours,website'
        $fFields += [uri]::EscapeDataString($contactFields)
    }
    if ($Atmosphere) {
        $atmosphereFields = ',price_level,rating,review,user_ratings_total'
        $fFields += [uri]::EscapeDataString($atmosphereFields)
    }
    $uri += $fFields

    if ($RegionBias) {
        Write-Debug -Message ('RegionBias: {0}' -f $RegionBias)
        $fRegion = '&region={0}' -f $RegionBias
        $uri += $fRegion
    }

    switch ($ReviewSort) {
        'MostRelevant' {
            $sortSelection = 'most_relevant'
        }
        'Newest' {
            $sortSelection = 'newest'
        }
    }
    Write-Debug -Message ('ReviewSort: {0}' -f $sortSelection)
    $uri += '&reviews_sort={0}' -f $sortSelection

    Write-Verbose -Message ('Querying Google API: {0}' -f $uri)

    Write-Debug -Message 'Adding API key'
    $fAPIKey = '&key={0}' -f $GoogleAPIKey
    $uri += $fAPIKey
    Write-Debug -Message ('Final URI: {0}' -f $uri)

    $invokeRestMethodSplat = @{
        Uri         = $uri
        ErrorAction = 'Stop'
    }
    try {
        $results = Invoke-RestMethod @invokeRestMethodSplat
    }
    catch {
        throw $_
    }

    if ($results.status -ne 'OK') {
        Write-Warning -Message 'Did not get a successful return from Google Geocode API endpoint'
        $finalResults = $results
    }
    else {
        $finalResults = ($results.result | Format-GMapPlaceDetail)
    }

    return $finalResults

} #Get-GMapPlaceDetail



<#
.EXTERNALHELP pwshPlaces-help.xml
#>

function Invoke-BingGeoCode {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            ParameterSetName = 'Address',
            HelpMessage = 'A string specifying the street line of an address')]
        [ValidateNotNullOrEmpty()]
        [string]$AddressLine,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Address',
            HelpMessage = 'The locality, such as the city or neighborhood, that corresponds to an address')]
        [ValidateNotNullOrEmpty()]
        [string]$City,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Address',
            HelpMessage = 'adminDistrict - A string that contains a subdivision, such as the abbreviation of a US state')]
        [ValidateNotNullOrEmpty()]
        [string]$State,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Address',
            HelpMessage = 'The post code, postal code, or ZIP Code of an address')]
        [ValidateNotNullOrEmpty()]
        [string]$PostalCode,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Address',
            HelpMessage = 'ISO Country code')]
        [ccTLD]$Country,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Location',
            HelpMessage = 'Geographic coordinate that specifies the north–south position of a point on the Earths surface')]
        [ValidateNotNullOrEmpty()]
        [string]$Latitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Location',
            HelpMessage = 'Geographic coordinate that specifies the east–west position of a point on the Earths surface')]
        [ValidateNotNullOrEmpty()]
        [string]$Longitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'textquery',
            HelpMessage = 'A string that contains information about a location, such as an address or landmark name')]
        [ValidateNotNullOrEmpty()]
        [string]$Query,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The language in which to return results')]
        [languages]$Language,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Specifies the maximum number of locations to return in the response')]
        [ValidateRange(1, 20)]
        [int]$MaxResults,

        [Parameter(Mandatory = $true,
            HelpMessage = 'Bing Maps API Key')]
        [ValidateNotNullOrEmpty()]
        [string]$BingMapsAPIKey
    )

    switch ($PSCmdlet.ParameterSetName) {
        'Address' {
            Write-Debug -Message 'Address specified'

            $uri = '{0}{1}' -f $bingMapsBaseURI, 'v1/Locations?output=json'
            Write-Debug -Message ('Base function URI: {0}' -f $uri)

            $fAddressLine = '&addressLine={0}' -f [uri]::EscapeDataString($AddressLine)
            $uri += $fAddressLine
            $fCity = '&locality={0}' -f [uri]::EscapeDataString($City)
            $uri += $fCity
            $fState = '&adminDistrict={0}' -f [uri]::EscapeDataString($State)
            $uri += $fState
            $fPostalCode = '&postalCode={0}' -f [uri]::EscapeDataString($PostalCode)
            $uri += $fPostalCode
            $fCountry = '&countryRegion={0}' -f [uri]::EscapeDataString($Country)
            $uri += $fCountry
        } #address
        'Location' {
            Write-Debug -Message 'Location specified'
            $latLong = 'v1/Locations/{0},{1}?output=json' -f $Latitude, $Longitude
            $uri = '{0}{1}' -f $bingMapsBaseURI, $latLong
            Write-Debug -Message ('Base function URI: {0}' -f $uri)
        } #location
        'textquery' {
            Write-Debug -Message 'Query specified'
            $uri = '{0}{1}' -f $bingMapsBaseURI, 'v1/Locations?output=json'
            Write-Debug -Message ('Base function URI: {0}' -f $uri)
            $fQuery = '&query={0}' -f [uri]::EscapeDataString($Query)
            $uri += $fQuery
        } #textquery
    } #switch_ParameterSetName

    if ($MaxResults) {
        Write-Debug -Message ('MaxResults: {0}' -f $MaxResults)
        $fMaxResults = '&maxResults={0}' -f $MaxResults
        $uri += $fMaxResults
    }
    if ($Language) {
        Write-Debug -Message ('Language: {0}' -f $Language)
        $fLanguage = '&culture={0}' -f $Language
        $uri += $fLanguage
    }

    Write-Verbose -Message ('Querying Bing API: {0}' -f $uri)

    Write-Debug -Message 'Adding API key'
    $fAPIKey = '&key={0}' -f $BingMapsAPIKey
    $uri += $fAPIKey
    Write-Debug -Message ('Final URI: {0}' -f $uri)

    $invokeRestMethodSplat = @{
        Uri         = $uri
        ErrorAction = 'Stop'
    }
    try {
        $results = Invoke-RestMethod @invokeRestMethodSplat
    }
    catch {
        throw $_
    }

    if ($results.statusDescription -ne 'OK') {
        Write-Warning -Message 'Did not get a successful return from Bing Location API endpoint'
        $finalResults = $results
    }
    elseif (-not ($results.resourceSets.estimatedTotal -ge 1)) {
        Write-Warning -Message 'No results returned from query'
        $finalResults = $results
    }
    else {
        $finalResults = ($results.resourceSets.resources | Format-BingGeoCode)
    }

    return $finalResults

} #Invoke-BingGeoCode



<#
.EXTERNALHELP pwshPlaces-help.xml
#>

function Invoke-GMapGeoCode {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            ParameterSetName = 'Address',
            HelpMessage = 'The street address or plus code that you want to geocode')]
        [ValidateNotNullOrEmpty()]
        [string]$Address,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Location',
            HelpMessage = 'Geographic coordinate that specifies the north–south position of a point on the Earths surface')]
        [ValidateNotNullOrEmpty()]
        [string]$Latitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Location',
            HelpMessage = 'Geographic coordinate that specifies the east–west position of a point on the Earths surface')]
        [ValidateNotNullOrEmpty()]
        [string]$Longitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'PlaceID',
            HelpMessage = 'The place ID of the place for which you wish to obtain the human-readable address')]
        [ValidateNotNullOrEmpty()]
        [string]$PlaceID,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The language in which to return results')]
        [languages]$Language,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The region code, specified as a ccTLD')]
        [ccTLD]$RegionBias,

        [Parameter(Mandatory = $true,
            HelpMessage = 'Google API Key')]
        [ValidateNotNullOrEmpty()]
        [string]$GoogleAPIKey
    )

    <#
        API Notes:
            LAT LONG LOOKUP
                Required parameters
                    address
                    key
                Optional parameters
                    bounds
                    language
                    region
                    components
                Direct API Example:
                    https://maps.googleapis.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA&key=YOUR_API_KEY
            Reverse geocoding (address lookup)
                Required parameters
                    latlng
                    key
                Optional parameters
                    language
                    region
                    result_type
                    location_type
    #>


    $uri = '{0}{1}' -f $googleMapsBaseURI, 'geocode/json?'
    Write-Debug -Message ('Base function URI: {0}' -f $uri)

    switch ($PSCmdlet.ParameterSetName) {
        'Address' {
            Write-Debug -Message 'Address specified'
            $fAddress = 'address={0}' -f [uri]::EscapeDataString($address)
            $uri += $fAddress
        } #address
        'Location' {
            Write-Debug -Message 'Location specified'
            $combinedLatLong = '{0},{1}' -f $Latitude, $Longitude
            $fLatLong = 'latlng={0}' -f [uri]::EscapeDataString($combinedLatLong)
            $uri += $fLatLong
        } #location
        'PlaceID' {
            Write-Debug -Message 'PlaceID specified'
            $fPlaceID = 'place_id={0}' -f $PlaceID
            $uri += $fPlaceID
        } #placeID
    } #switch_ParameterSetName

    if ($Language) {
        Write-Debug -Message ('Language: {0}' -f $Language)
        $fLanguage = '&language={0}' -f $Language
        $uri += $fLanguage
    }
    if ($RegionBias) {
        Write-Debug -Message ('RegionBias: {0}' -f $RegionBias)
        $fRegion = '&region={0}' -f $RegionBias
        $uri += $fRegion
    }

    Write-Verbose -Message ('Querying Google API: {0}' -f $uri)

    Write-Debug -Message 'Adding API key'
    $fAPIKey = '&key={0}' -f $GoogleAPIKey
    $uri += $fAPIKey
    Write-Debug -Message ('Final URI: {0}' -f $uri)

    $invokeRestMethodSplat = @{
        Uri         = $uri
        ErrorAction = 'Stop'
    }
    try {
        $results = Invoke-RestMethod @invokeRestMethodSplat
    }
    catch {
        throw $_
    }

    if ($results.status -ne 'OK') {
        Write-Warning -Message 'Did not get a successful return from Google Geocode API endpoint'
        $finalResults = $results
    }
    else {
        $finalResults = ($results.results | Format-GMapGeoCode)
        # $finalResults = $results.results
        # $finalResults = (Format-GMapGeoCode -Results $Results.results)
    }

    return $finalResults

} #Invoke-GMapGeoCode



<#
.EXTERNALHELP pwshPlaces-help.xml
#>

function Search-BingNearbyPlace {
    [CmdletBinding(
        PositionalBinding = $false,
        DefaultParameterSetName = 'PlaceType')]
    param (

        [Parameter(Mandatory = $true,
            ParameterSetName = 'PlaceType',
            HelpMessage = 'Restricts the results to places matching the specified type')]
        [Parameter(ParameterSetName = 'Point', Mandatory = $false)]
        [Parameter(ParameterSetName = 'Circle', Mandatory = $false)]
        [Parameter(ParameterSetName = 'Rectangle', Mandatory = $false)]
        [typeIdentifier]$Type,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Point',
            HelpMessage = 'Location bias by point lat')]
        [ValidateNotNullOrEmpty()]
        [string]$PointLatitude,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Point',
            HelpMessage = 'Location bias by point long')]
        [ValidateNotNullOrEmpty()]
        [string]$PointLongitude,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Circle',
            HelpMessage = 'Location bias circular lat')]
        [ValidateNotNullOrEmpty()]
        [string]$CircleLatitude,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Circle',
            HelpMessage = 'Location bias circular long')]
        [ValidateNotNullOrEmpty()]
        [string]$CircleLongitude,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Circle',
            HelpMessage = 'Location bias circular radius')]
        [ValidateRange(0, 5000)]
        [string]$CircleRadius,

        # A string specifying two lat/lng pairs in decimal degrees, representing the south/west and north/east points of a rectangle.
        [Parameter(Mandatory = $true,
            ParameterSetName = 'Rectangle',
            HelpMessage = 'Location bias rectangle south lat')]
        [ValidateNotNullOrEmpty()]
        [string]$SouthLatitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Rectangle',
            HelpMessage = 'Location bias rectangle west long')]
        [ValidateNotNullOrEmpty()]
        [string]$WestLongitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Rectangle',
            HelpMessage = 'Location bias rectangle north lat')]
        [ValidateNotNullOrEmpty()]
        [string]$NorthLatitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Rectangle',
            HelpMessage = 'Location bias rectangle east long')]
        [ValidateNotNullOrEmpty()]
        [string]$EastLongitude,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The region code, specified as a ccTLD')]
        [ccTLD]$RegionBias,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The language in which to return results')]
        [languages]$Language,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Specifies the maximum number of locations to return in the response')]
        [ValidateRange(1, 20)]
        [int]$MaxResults,

        [Parameter(Mandatory = $true,
            HelpMessage = 'Bing Maps API Key')]
        [ValidateNotNullOrEmpty()]
        [string]$BingMapsAPIKey
    )

    Write-Debug -Message ($PSCmdlet.ParameterSetName)

    $uri = '{0}{1}' -f $bingMapsBaseURI, 'v1/LocalSearch?output=json'
    Write-Debug -Message ('Base function URI: {0}' -f $uri)

    $fType = '&type={0}' -f [uri]::EscapeDataString($type)
    $uri += $fType

    switch ($PSCmdlet.ParameterSetName) {
        'Point' {
            Write-Debug -Message 'Point specified'
            $combinedPoint = '{0},{1}' -f $PointLatitude, $PointLongitude
            $fLocationBias = '&userLocation={0}' -f [uri]::EscapeDataString($combinedPoint)
            $uri += $fLocationBias
        } #point
        'Circle' {
            Write-Debug -Message 'Circle specified'
            $combinedCircle = '{0},{1},{2}' -f $CircleLatitude, $CircleLongitude, $CircleRadius
            $fLocationBias = '&userCircularMapView={0}' -f [uri]::EscapeDataString($combinedCircle)
            $uri += $fLocationBias
        } #circle
        'Rectangle' {
            Write-Debug -Message 'Rectangle specified'
            $combinedRectangle = '{0},{1},{2},{3}' -f $SouthLatitude, $WestLongitude, $NorthLatitude, $EastLongitude
            $fLocationBias = '&userMapView={0}' -f [uri]::EscapeDataString($combinedRectangle)
            $uri += $fLocationBias
        } #rectangle
    } #switch_ParameterSetName

    if ($RegionBias) {
        Write-Debug -Message ('RegionBias: {0}' -f $RegionBias)
        $fRegion = '&userRegion={0}' -f $RegionBias
        $uri += $fRegion
    }
    if ($MaxResults) {
        Write-Debug -Message ('MaxResults: {0}' -f $MaxResults)
        $fMaxResults = '&maxResults={0}' -f $MaxResults
        $uri += $fMaxResults
    }
    if ($Language) {
        Write-Debug -Message ('Language: {0}' -f $Language)
        $fLanguage = '&culture={0}' -f $Language
        $uri += $fLanguage
    }

    Write-Verbose -Message ('Querying Bing API: {0}' -f $uri)

    Write-Debug -Message 'Adding API key'
    $fAPIKey = '&key={0}' -f $BingMapsAPIKey
    $uri += $fAPIKey

    $invokeRestMethodSplat = @{
        Uri         = $uri
        ErrorAction = 'Stop'
    }
    try {
        $results = Invoke-RestMethod @invokeRestMethodSplat
    }
    catch {
        throw $_
    }

    if ($results.statusDescription -ne 'OK') {
        Write-Warning -Message 'Did not get a successful return from Bing Location API endpoint'
        $finalResults = $results
    }
    elseif (-not ($results.resourceSets.estimatedTotal -ge 1)) {
        Write-Warning -Message 'No results returned from query'
        $finalResults = $results
    }
    else {
        $finalResults = ($results.resourceSets.resources | Format-BingPlace)
    }

    return $finalResults

} #Search-BingNearbyPlace



<#
.EXTERNALHELP pwshPlaces-help.xml
#>

function Search-GMapNearbyPlace {
    [CmdLetBinding(
        DefaultParameterSetName = 'Location')]
    param (
        [Parameter(Mandatory = $true,
            ParameterSetName = 'Location',
            HelpMessage = 'Geographic coordinate that specifies the north–south position of a point on the Earths surface')]
        [Parameter(ParameterSetName = 'Area', Mandatory = $true)]
        [Parameter(ParameterSetName = 'Distance', Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Latitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Location',
            HelpMessage = 'Geographic coordinate that specifies the east–west position of a point on the Earths surface')]
        [Parameter(ParameterSetName = 'Area', Mandatory = $true)]
        [Parameter(ParameterSetName = 'Distance', Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Longitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Area',
            HelpMessage = 'Distance (in meters) within which to return place results')]
        [Parameter(ParameterSetName = 'Location', Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Radius,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Area',
            HelpMessage = 'This option sorts results based on their importance')]
        # [Parameter(ParameterSetName = 'Location', Mandatory = $false)]
        [switch]$RankByProminence,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Distance',
            HelpMessage = 'This option biases search results in ascending order by their distance from the specified location')]
        # [Parameter(ParameterSetName = 'Location', Mandatory = $false)]
        [switch]$RankByDistance,

        [Parameter(Mandatory = $false,
            HelpMessage = 'A term to be matched against all content that Google has indexed for this place')]
        [ValidateNotNullOrEmpty()]
        [string]$Keyword,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Restricts the results to places matching the specified type')]
        [placeTypes]$Type,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The language in which to return results')]
        [languages]$Language,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Returns only those places that are open for business at the time the query is sent')]
        [switch]$OpenNow,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Restricts results to only those places within the specified range')]
        [ValidateNotNullOrEmpty()]
        [string]$MaxPrice,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Restricts results to only those places within the specified range')]
        [ValidateNotNullOrEmpty()]
        [string]$MinPrice,

        [Parameter(Mandatory = $false,
            HelpMessage = 'TBD')]
        [switch]$AllSearchResults,

        [Parameter(Mandatory = $true,
            HelpMessage = 'Google API Key')]
        [ValidateNotNullOrEmpty()]
        [string]$GoogleAPIKey
    )

    <#
        API Notes:
            Required parameters
                location
                    This must be specified as latitude,longitude.
                radius
                    Defines the distance (in meters) within which to return place results. You may bias results to a specified circle by passing a location and a radius parameter.
                    Doing so instructs the Places service to prefer showing results within that circle; results outside of the defined area may still be displayed.
                    The radius will automatically be clamped to a maximum value depending on the type of search and other parameters.
                    Query Autocomplete: 50,000 meters
                    Text Search: 50,000 meters
            Optional parameters
                keyword
                language
                maxprice
                minprice
                opennow
                pagetoken
                radius
                rankby
                type
    #>


    $uri = '{0}{1}' -f $googleMapsBaseURI, 'place/nearbysearch/json?'
    Write-Debug -Message ('Base function URI: {0}' -f $uri)

    $combinedPoint = '{0},{1}' -f $Latitude, $Longitude
    $fLocation = 'location={0}' -f [uri]::EscapeDataString($combinedPoint)
    $uri += $fLocation

    if ($Radius) {
        Write-Debug -Message ('Radius: {0}' -f $Radius)
        $fRadius = '&radius={0}' -f $Radius
        $uri += $fRadius
    }

    if ($Keyword) {
        Write-Debug -Message ('Keyword: {0}' -f $Keyword)
        $fKeyword = '&keyword={0}' -f $Keyword
        $uri += $fKeyword
    }

    if ($Type) {
        Write-Debug -Message ('Type: {0}' -f $Type)
        $fType = '&type={0}' -f $Type
        $uri += $fType
    }

    if ($Language) {
        Write-Debug -Message ('Language: {0}' -f $Language)
        $fLanguage = '&language={0}' -f $Language
        $uri += $fLanguage
    }

    if ($OpenNow) {
        $cOpenNow = 'openNow:true'
        $fOpenNow = '&{0}' -f [uri]::EscapeDataString($cOpenNow)
        $uri += $fOpenNow
    }

    if ($RankByProminence) {
        $fRankBy = '&rankby=prominence'
        $uri += $fRankBy
    }

    if ($RankByDistance) {
        $fRankBy = '&rankby=distance'
        $uri += $fRankBy
    }

    if ($MaxPrice) {
        Write-Debug -Message ('MaxPrice: {0}' -f $MaxPrice)
        $fMaxPrice = '&maxprice={0}' -f $MaxPrice
        $uri += $fMaxPrice
    }

    if ($MinPrice) {
        Write-Debug -Message ('MinPrice: {0}' -f $MinPrice)
        $fMinPrice = '&minprice={0}' -f $MinPrice
        $uri += $fMinPrice
    }

    Write-Verbose -Message ('Querying Google API: {0}' -f $uri)

    Write-Debug -Message 'Adding API key'
    $fAPIKey = '&key={0}' -f $GoogleAPIKey
    $uri += $fAPIKey
    Write-Debug -Message ('Final URI: {0}' -f $uri)

    $allResults = New-Object System.Collections.Generic.List[object]

    $invokeRestMethodSplat = @{
        Uri         = $uri
        ErrorAction = 'Stop'
    }
    try {
        $results = Invoke-RestMethod @invokeRestMethodSplat
    }
    catch {
        throw $_
    }

    if ($results.status -eq 'OK' -and $AllSearchResults) {
        $results.results | ForEach-Object {
            [void]$allResults.Add($_)
        }
        $i = 0
        $pageToken = $results.next_page_token
        while ($null -ne $pageToken) {
            Write-Debug -Message ('Run # - {0}' -f $i)
            #_________________________________________________________________________
            # reconstruct URI for pagetoken use
            $loopURI = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json?'
            $loopURI += 'pagetoken={0}' -f $pageToken
            $loopURI += $fAPIKey
            #_________________________________________________________________________
            <#
            There is a short delay between when a next_page_token is issued, and when it will become valid.
            Requesting the next page before it is available will return an INVALID_REQUEST response.
            This sleep is necessary. the api backend needs time to catch up.
            TODO: add logic to check for INVALID_REQUEST and retry
            #>

            Start-Sleep -Seconds 4
            #_________________________________________________________________________
            # resets
            $results = $null
            $pageToken = $null
            #_________________________________________________________________________
            $invokeRestMethodSplat = @{
                Uri         = $loopURI
                ErrorAction = 'Stop'
            }
            try {
                $results = Invoke-RestMethod @invokeRestMethodSplat
            }
            catch {
                throw $_
            }
            #_________________________________________________________________________
            $pageToken = $results.next_page_token
            $results.results | ForEach-Object {
                [void]$allResults.Add($_)
            }
            $i++
            #_________________________________________________________________________
        }
        $finalResults = $allResults | Format-GMapNearbyPlace
    }
    elseif ($results.status -ne 'OK' ) {
        Write-Warning -Message 'Did not get a succcessful return from Google Geocode API endpoint'
        $finalResults = $results
    }
    else {
        $finalResults = ($results.results | Format-GMapNearbyPlace)
    }

    return $finalResults

} #Search-GMapNearbyPlace



<#
.EXTERNALHELP pwshPlaces-help.xml
#>

function Search-GMapText {
    [CmdletBinding(
        DefaultParameterSetName = 'textquery')]
    param (
        [Parameter(Mandatory = $true,
            ParameterSetName = 'textquery',
            HelpMessage = 'Text string on which to search')]
        [Parameter(ParameterSetName = 'Location', Mandatory = $false)]
        [Parameter(ParameterSetName = 'Area', Mandatory = $false)]
        [Parameter(ParameterSetName = 'Distance', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$Query,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Location',
            HelpMessage = 'Geographic coordinate that specifies the north–south position of a point on the Earths surface')]
        [Parameter(ParameterSetName = 'Area', Mandatory = $false)]
        [Parameter(ParameterSetName = 'Distance', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$Latitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Location',
            HelpMessage = 'Geographic coordinate that specifies the east–west position of a point on the Earths surface')]
        [Parameter(ParameterSetName = 'Area', Mandatory = $false)]
        [Parameter(ParameterSetName = 'Distance', Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$Longitude,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Area',
            HelpMessage = 'Distance (in meters) within which to return place results')]
        [ValidateNotNullOrEmpty()]
        [string]$Radius,

        [Parameter(Mandatory = $false,
            ParameterSetName = 'Area',
            HelpMessage = 'This option sorts results based on their importance')]
        [switch]$RankByProminence,

        [Parameter(Mandatory = $true,
            ParameterSetName = 'Distance',
            HelpMessage = 'This option biases search results in ascending order by their distance from the specified location')]
        [switch]$RankByDistance,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Restricts the results to places matching the specified type')]
        [placeTypes]$Type,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The language in which to return results')]
        [languages]$Language,

        [Parameter(Mandatory = $false,
            HelpMessage = 'The region code, specified as a ccTLD')]
        [ccTLD]$RegionBias,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Returns only those places that are open for business at the time the query is sent')]
        [switch]$OpenNow,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Restricts results to only those places within the specified range')]
        [ValidateNotNullOrEmpty()]
        [string]$MaxPrice,

        [Parameter(Mandatory = $false,
            HelpMessage = 'Restricts results to only those places within the specified range')]
        [ValidateNotNullOrEmpty()]
        [string]$MinPrice,

        [Parameter(Mandatory = $false,
            HelpMessage = 'By default 20 results are returned from a standard search. Using this switch increases the search results from 20 to the maximum of 60')]
        [switch]$AllSearchResults,

        [Parameter(Mandatory = $true,
            HelpMessage = 'Google API Key')]
        [ValidateNotNullOrEmpty()]
        [string]$GoogleAPIKey
    )

    <#
        API Notes:
            Required parameters
                query
                radius
                    Defines the distance (in meters) within which to return place results. You may bias results to a specified circle by passing a location and a radius parameter.
                    Doing so instructs the Places service to prefer showing results within that circle; results outside of the defined area may still be displayed.
                    The radius will automatically be clamped to a maximum value depending on the type of search and other parameters.
                    Query Autocomplete: 50,000 meters
                    Text Search: 50,000 meters
            Optional parameters
                language
                location
                maxprice
                minprice
                opennow
                pagetoken
                region
                type
    #>


    $uri = '{0}{1}' -f $googleMapsBaseURI, 'place/textsearch/json?'
    Write-Debug -Message ('Base function URI: {0}' -f $uri)

    $fQuery = 'query={0}' -f [uri]::EscapeDataString($Query)
    $uri += $fQuery

    if ($Latitude -and $Longitude) {
        $combinedPoint = '{0},{1}' -f $Latitude, $Longitude
        $fLocation = '&location={0}' -f [uri]::EscapeDataString($combinedPoint)
        $uri += $fLocation
    }

    if ($Radius) {
        Write-Debug -Message ('Radius: {0}' -f $Radius)
        $fRadius = '&radius={0}' -f $Radius
        $uri += $fRadius
    }

    if ($Type) {
        Write-Debug -Message ('Type: {0}' -f $Type)
        $fType = '&type={0}' -f $Type
        $uri += $fType
    }

    if ($Language) {
        Write-Debug -Message ('Language: {0}' -f $Language)
        $fLanguage = '&language={0}' -f $Language
        $uri += $fLanguage
    }

    if ($RegionBias) {
        Write-Debug -Message ('RegionBias: {0}' -f $RegionBias)
        $fRegion = '&region={0}' -f $RegionBias
        $uri += $fRegion
    }

    if ($OpenNow) {
        $cOpenNow = 'openNow:true'
        $fOpenNow = '&{0}' -f [uri]::EscapeDataString($cOpenNow)
        $uri += $fOpenNow
    }

    if ($RankByProminence) {
        $fRankBy = '&rankby=prominence'
        $uri += $fRankBy
    }

    if ($RankByDistance) {
        $fRankBy = '&rankby=distance'
        $uri += $fRankBy
    }

    if ($MaxPrice) {
        Write-Debug -Message ('MaxPrice: {0}' -f $MaxPrice)
        $fMaxPrice = '&maxprice={0}' -f $MaxPrice
        $uri += $fMaxPrice
    }

    if ($MinPrice) {
        Write-Debug -Message ('MinPrice: {0}' -f $MinPrice)
        $fMinPrice = '&minprice={0}' -f $MinPrice
        $uri += $fMinPrice
    }

    Write-Verbose -Message ('Querying Google API: {0}' -f $uri)

    Write-Debug -Message 'Adding API key'
    $fAPIKey = '&key={0}' -f $GoogleAPIKey
    $uri += $fAPIKey
    Write-Debug -Message ('Final URI: {0}' -f $uri)

    $allResults = New-Object System.Collections.Generic.List[object]

    $invokeRestMethodSplat = @{
        Uri         = $uri
        ErrorAction = 'Stop'
    }
    try {
        $results = Invoke-RestMethod @invokeRestMethodSplat
    }
    catch {
        throw $_
    }

    if ($results.status -eq 'OK' -and $AllSearchResults) {
        $results.results | ForEach-Object {
            [void]$allResults.Add($_)
        }
        $i = 0
        $pageToken = $results.next_page_token
        while ($null -ne $pageToken) {
            Write-Debug -Message ('Run # - {0}' -f $i)
            #_________________________________________________________________________
            # reconstruct URI for pagetoken use
            $loopURI = 'https://maps.googleapis.com/maps/api/place/textsearch/json?'
            $loopURI += 'pagetoken={0}' -f $pageToken
            $loopURI += $fAPIKey
            #_________________________________________________________________________
            <#
            There is a short delay between when a next_page_token is issued, and when it will become valid.
            Requesting the next page before it is available will return an INVALID_REQUEST response.
            This sleep is necessary. the api backend needs time to catch up.
            TODO: add logic to check for INVALID_REQUEST and retry
            #>

            Start-Sleep -Seconds 4
            #_________________________________________________________________________
            # resets
            $results = $null
            $pageToken = $null
            #_________________________________________________________________________
            $invokeRestMethodSplat = @{
                Uri         = $loopURI
                ErrorAction = 'Stop'
            }
            try {
                $results = Invoke-RestMethod @invokeRestMethodSplat
            }
            catch {
                throw $_
            }
            #_________________________________________________________________________
            $pageToken = $results.next_page_token
            $results.results | ForEach-Object {
                [void]$allResults.Add($_)
            }
            $i++
            #_________________________________________________________________________
        }
        $finalResults = $allResults | Format-GMapPlaceText
    }
    elseif ($results.status -ne 'OK' ) {
        Write-Warning -Message 'Did not get a succcessful return from Google Geocode API endpoint'
        $finalResults = $results
    }
    else {
        $finalResults = ($results.results | Format-GMapPlaceText)
    }

    return $finalResults

} #Search-GMapText