Recently I had the need to geo-encode millions of addresses, after reviewing multiple technologies I found PowerShell provided excellent results. Here is the example solution.
You will need a Bing Maps Maps Account API Key and Microsoft SQL Server
The first step is to create a database with sample data, below is the script:
USE [master]
GO
/****** Object: Database [LocationDB] Script Date: 2012-10-20 12:42:43 AM ******/
CREATE DATABASE [LocationDB]
GO
ALTER DATABASE [LocationDB] SET COMPATIBILITY_LEVEL = 110
GO
IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))
begin
EXEC [LocationDB].[dbo].[sp_fulltext_database] @action = 'enable'
end
GO
ALTER DATABASE [LocationDB] SET ANSI_NULL_DEFAULT OFF
GO
ALTER DATABASE [LocationDB] SET ANSI_NULLS OFF
GO
ALTER DATABASE [LocationDB] SET ANSI_PADDING OFF
GO
ALTER DATABASE [LocationDB] SET ANSI_WARNINGS OFF
GO
ALTER DATABASE [LocationDB] SET ARITHABORT OFF
GO
ALTER DATABASE [LocationDB] SET AUTO_CLOSE OFF
GO
ALTER DATABASE [LocationDB] SET AUTO_CREATE_STATISTICS ON
GO
ALTER DATABASE [LocationDB] SET AUTO_SHRINK OFF
GO
ALTER DATABASE [LocationDB] SET AUTO_UPDATE_STATISTICS ON
GO
ALTER DATABASE [LocationDB] SET CURSOR_CLOSE_ON_COMMIT OFF
GO
ALTER DATABASE [LocationDB] SET CURSOR_DEFAULT GLOBAL
GO
ALTER DATABASE [LocationDB] SET CONCAT_NULL_YIELDS_NULL OFF
GO
ALTER DATABASE [LocationDB] SET NUMERIC_ROUNDABORT OFF
GO
ALTER DATABASE [LocationDB] SET QUOTED_IDENTIFIER OFF
GO
ALTER DATABASE [LocationDB] SET RECURSIVE_TRIGGERS OFF
GO
ALTER DATABASE [LocationDB] SET DISABLE_BROKER
GO
ALTER DATABASE [LocationDB] SET AUTO_UPDATE_STATISTICS_ASYNC OFF
GO
ALTER DATABASE [LocationDB] SET DATE_CORRELATION_OPTIMIZATION OFF
GO
ALTER DATABASE [LocationDB] SET TRUSTWORTHY OFF
GO
ALTER DATABASE [LocationDB] SET ALLOW_SNAPSHOT_ISOLATION OFF
GO
ALTER DATABASE [LocationDB] SET PARAMETERIZATION SIMPLE
GO
ALTER DATABASE [LocationDB] SET READ_COMMITTED_SNAPSHOT OFF
GO
ALTER DATABASE [LocationDB] SET HONOR_BROKER_PRIORITY OFF
GO
ALTER DATABASE [LocationDB] SET RECOVERY FULL
GO
ALTER DATABASE [LocationDB] SET MULTI_USER
GO
ALTER DATABASE [LocationDB] SET PAGE_VERIFY CHECKSUM
GO
ALTER DATABASE [LocationDB] SET DB_CHAINING OFF
GO
ALTER DATABASE [LocationDB] SET FILESTREAM( NON_TRANSACTED_ACCESS = OFF )
GO
ALTER DATABASE [LocationDB] SET TARGET_RECOVERY_TIME = 0 SECONDS
GO
EXEC sys.sp_db_vardecimal_storage_format N'LocationDB', N'ON'
GO
USE [LocationDB]
GO
/****** Object: StoredProcedure [dbo].[usp_GetLocations] Script Date: 2012-10-20 12:42:44 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Nathan Storms
-- Create date: 2012-10-20
-- Description: Get Location that need encoding.
-- =============================================
CREATE PROCEDURE [dbo].[usp_GetLocations]
-- Add the parameters for the stored procedure here
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT TOP 1000 Id, ([Address] + ', ' + [City] + ', ' + [Region] + ', ' + [Country] + ', ' + [PostalCode]) AS Address
FROM [dbo].[Locations] WITH (NOLOCK) WHERE SRID IS NULL
END
GO
/****** Object: StoredProcedure [dbo].[usp_UpdateLocation] Script Date: 2012-10-20 12:42:44 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Nathan Storms
-- Create date: 2012-10-20
-- Description: Update locations with geocode
-- =============================================
CREATE PROCEDURE [dbo].[usp_UpdateLocation]
-- Add the parameters for the stored procedure here
@ID int = 0,
@Longitude float = 0,
@Latitude float = 0,
@SRID int = 4326
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
UPDATE [dbo].[Locations]
SET [Latitude] = @Latitude
,[Longitude] = @Longitude
,[SRID] = 4326
WHERE [ID] = @ID
END
GO
/****** Object: Table [dbo].[Locations] Script Date: 2012-10-20 12:42:44 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Locations](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Address] [varchar](50) NOT NULL,
[City] [varchar](50) NOT NULL,
[Region] [char](2) NOT NULL,
[Country] [varchar](50) NOT NULL,
[PostalCode] [char](15) NOT NULL,
[Latitude] [float] NULL,
[Longitude] [float] NULL,
[SRID] [int] NULL,
[Point] AS ([geography]::Point([Latitude],[Longitude],[SRID])),
CONSTRAINT [PK_Locations] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Locations] ADD CONSTRAINT [DF_Locations_Latitude] DEFAULT ((0)) FOR [Latitude]
GO
ALTER TABLE [dbo].[Locations] ADD CONSTRAINT [DF_Locations_Longitude] DEFAULT ((0)) FOR [Longitude]
GO
INSERT INTO [dbo].[Locations]
([Address],[City],[Region],[Country],[PostalCode])
VALUES
('6258 Amesbury St','San Diego','CA','United States','92114-6717'),
('8308 Fenway Rd','Bethesda','MD','United States','20817-2733'),
('10 Wall St','Burlington','MA','United States','01803-4749'),
('3315 W Greenway Rd','Phoenix','AZ','United States','85053-380'),
('1635 Camile Pl','Santa Ana','CA','United States','92703-4401')
GO
Lastly here is the PowerShell Script to geo-encode the addresses in the database:
# Setup Connection To Bing
$key = "{Add Bing Map API Key}"
$ws = New-WebServiceProxy -uri http://dev.virtualearth.net/webservices/v1/geocodeservice/geocodeservice.svc?wsdl
$wsgr = New-Object Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy1ervice_geocodeservice_svc_wsdl.GeocodeRequest
$wsc = New-Object Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy1ervice_geocodeservice_svc_wsdl.Credentials
$wsc.ApplicationId = $key
$wsgr.Credentials = $wsc
# Setup Connection To SQL Database
function ConnectSQL {
Param ($server, $query, $database)
$conn = new-object ('System.Data.SqlClient.SqlConnection')
$connString = "Server=$server;Integrated Security=SSPI;Database=$database"
$conn.ConnectionString = $connString
$conn.Open()
$sqlCmd = New-Object System.Data.SqlClient.SqlCommand
$sqlCmd.CommandText = $query
$sqlCmd.Connection = $conn
$Rset = $sqlCmd.ExecuteReader()
,$Rset ## The comma is used to create an outer array, which PS strips off automatically when returning the $Rset
}
function QuerySQL {
Param ($server, $query, $database = "master")
$data = ConnectSQL $server $query $database
while ($data.read() -eq $true) {
$max = $data.FieldCount -1
$obj = New-Object Object
For ($i = 0; $i -le $max; $i++) {
$name = $data.GetName($i)
$obj | Add-Member Noteproperty $name -value $data.GetValue($i)
}
$obj
}
}
Write-Host "Getting Records From Database..."
$Locations = QuerySQL "localhost" "EXECUTE [dbo].[usp_GetLocations]" "LocationDB"
$Locations | ForEach-Object {
$wsr = 0
$Longitude = 0
$Latitude = 0
$ID = $_.ID
$Address = $_.Address
Write-Host "Processing record $LocationID for the address: $Address"
$wsgr.Query = $Address
$wsr = $ws.Geocode($wsgr)
$Longitude = $wsr.Results[0].Locations[0].Longitude
$Latitude = $wsr.Results[0].Locations[0].Latitude
Write-Host "Location found for record $ID the location is $Longitude, $Latitude"
QuerySQL "localhost" "EXECUTE [dbo].[usp_UpdateLocation] $ID, $Longitude, $Latitude" "LocationDB"
}
That’s it.