using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; using System.Configuration; using System.Data; using System.Xml; using System.Xml.Linq; using System.Net; using System.IO; using ApnTools; using System.Data.Sql; // // Sample code for loading permit data into a point layer using the ParcelStream API. // // This code uses the following external classes: // DataReader: The purpose of this class is to hide the form of the incoming permit data. It may be in a csv file or // a database, or it may even come from a webservice api to a permit application. // ApnResolver: This contains the logging of resolving an APN to a parcel or property record. // // This code assumes that a layer called LG8DEMO.JWILSON/PERMITS has been created already with fields that correspond with the // DataTable created below. // // TODO: Make the layer name a constant declared near the top of the program. // TODO: Make the Book/Page length of the APN a constant that is passed to ApnResolver. // namespace PermitSample { class Program { // GetXML makes the http request in sQueryString and returns the response. private static XmlDocument GetXML(string sQueryString) { HttpWebRequest oRequest = (HttpWebRequest)WebRequest.Create(sQueryString); HttpWebResponse oResponse = (HttpWebResponse)oRequest.GetResponse(); XmlDocument oResponseXML = new XmlDocument(); oResponseXML.Load(oResponse.GetResponseStream()); return (oResponseXML); } // ParseDate takes a string and turns it into a DateTime if possible, otherwise it returns DBNull. private static object ParseDate(string s) { DateTime rslt; DateTime.TryParse(s, out rslt); if (rslt.Ticks == 0) return (object)DBNull.Value; return rslt; } // BuildGeo takes a result returned from the LookupApn function and formats the data into a string that is parsed // in WriteRecords to create the GeoLink to the geographic info. private static string BuildGeo(ApnResult rslt) { if (rslt.Match_Type == MatchType.Parcel) return @"*SS.Prop.ParcelDetail/ParcelDetail|LOCATION_ID|" + rslt.Location_Id; else if (rslt.Match_Type == MatchType.Property) return @"*SS.Prop.PropertyDetail/PropertyDetail|TAX_ID|" + rslt.Tax_Id; else if (rslt.Match_Type == MatchType.MultiParcel) return "+" + "POINT(" + rslt.Longitude + " " + rslt.Latitude + ")"; else return null; } // BuildMatchedTo tranlates the MatchType result to a string for the ApnMatchedTo database field. private static string BuildMatchedTo(ApnResult rslt) { if (rslt.Match_Type == MatchType.Parcel) return "PARCEL"; else if (rslt.Match_Type == MatchType.Property) return "PROPERTY"; else if (rslt.Match_Type == MatchType.MultiParcel) return "MULTI"; else if (rslt.Match_Type == MatchType.None) return "NONE"; else return "ERROR"; } // BuildMatchedMulti creates the value for the ApnMatchedMulti database field based on the ApnResult private static string BuildMatchedMulti(ApnResult rslt) { if (rslt.Match_Type == MatchType.MultiParcel) return rslt.Location_Id; else return null; } static void Main(string[] args) { // // Set up a file to log the results of resolving the apns // StreamWriter logFS = new StreamWriter(@"\\digmap.local\Users\jwilson\My Documents\apnLog.txt"); int exactMatch = 0; int partialMatch = 0; int noMatch = 0; string sCurrent = "Starting"; // GetSIK / InitSession // Create Version Entry // build a table of transactions to create the DMP permit layer // for each record of the local permit data // add a row to table using that data // use ApnResolver to get a Location_Id and/or a Tax_Id to add to the row // for each row in table // construct the XML for the transaction from table // invoke transaction.aspx try { sCurrent = "Calling getSIK"; // getSIK - this does the authentication that creates a "Session Initialization Key" // server IP must be registered with DMP for this to work string sQueryString = "http://dc1.parcelstream.com/admin/getSIK.aspx?login=jwilson&account=LG8Demo"; XmlDocument oResponseXML = GetXML(sQueryString); XmlNode oNode = oResponseXML.SelectSingleNode("Response/Success"); if (oNode == null) throw (new Exception("Could not authenticate: " + oResponseXML.SelectSingleNode("Response/Error/@message").Value)); // Get Authentication site and key // Success response looks like: // // // // message format is "///" // need to separate the components to generate the input to InitSession string[] sAuth = oNode.Attributes["message"].Value.Split(new char[] { '/' }); string sDC = sAuth[1]; string sKey = sAuth[2] + "/" + sAuth[3]; // // string sFold = sAuth[2]; // Now construct InitSession call // InitSession can only be called once per "Session Initialization Key" // the idea is only a trusted server can call getSIK but then the client app calls initSession // InitSession returns "SS_CANDY" that can be used as a cookie or as part of the request string // Request // http://dc1.parcelstream.com/_T195/InitSession.aspx?sik=_T195/3af82f3b-d8b5-4353-8fdd-edb2206ec300-100850_275307 // Successful Response // // // // // // // // Error Response // // // sCurrent = "Calling InitSession"; sQueryString = string.Format("http://{0}.parcelstream.com/{1}/InitSession.aspx?sik={2}", sDC, sFold, sKey); oResponseXML = GetXML(sQueryString); oNode = oResponseXML.SelectSingleNode("Response/Results"); if (oNode == null) throw (new Exception("Could not Initialize Session: " + oResponseXML.SelectSingleNode("Response/Error/@message").Value)); XmlNode oRowNode = oNode.SelectSingleNode("//Row"); string sCandy = oRowNode.Attributes["Candy"].Value; string[] sHosts = oRowNode.Attributes["Domains"].Value.Split(new char[] { ',', ';' }); // used to indicate what data centers are available for this account // if an account is setup to have replicated data in multiple datacenters then this could be a list of domains and the client // could enhance availability and performance by testing and choosing based on latency and availability // this is typically not required because the dns service can automatically select best data center // just use the first host in the list // for each row in input data resolve the apn and add info to table String version; string apn; DataReader reader = new DataReader(); if (reader != null) { // Create a new version entry - required to use transaction engine sQueryString = String.Format("{0}version.aspx?&action=create&recseq=0&SS_CANDY={1}&name=InitialLoad&desc=loaded by APNLoader Program from master watch list, MasterList.csv", sHosts[0], sCandy); oResponseXML = GetXML(sQueryString); version = ""; // versionResults.Root.Attribute("versionId").Value.ToString(); if (oResponseXML.SelectSingleNode("Response/@versionId") != null) { version = oResponseXML.SelectSingleNode("Response/@versionId").Value; } else { throw new ApplicationException("Unable to get a version id."); } } else throw new ApplicationException("Could not open CSV"); DataTable table = new DataTable(); table.Columns.Add("_GEOID", typeof(string)); table.Columns.Add("APN", typeof(string)); table.Columns.Add("APNMatchTo", typeof(string)); table.Columns.Add("APNMatchMulti", typeof(string)); table.Columns.Add("APNMatchAccuracyFt", typeof(int)); table.Columns.Add("PermitNum", typeof(string)); table.Columns.Add("Issued", typeof(DateTime)); table.Columns.Add("Expired", typeof(DateTime)); table.Columns.Add("PermitType", typeof(string)); table.Columns.Add("PermitStatus", typeof(string)); table.Columns.Add("Fees", typeof(string)); table.Columns.Add("LocationId", typeof(string)); table.Columns.Add("TaxId", typeof(string)); table.Columns.Add("PermitOwner1", typeof(string)); table.Columns.Add("PermitOwner2", typeof(string)); table.Columns.Add("PermitAddress", typeof(string)); table.Columns.Add("PermitCityAndState", typeof(string)); table.Columns.Add("PermitZipCode", typeof(string)); int recseq = 1; ApnParams p = new ApnParams(); p.sHosts = sHosts; p.sCandy = sCandy; p.ApnLookupLayer = "SS.Prop.APNLookup/APNLookup"; p.ParcelLayer = "SS.Prop.ParcelDetail/ParcelDetail"; p.PropertyLayer = "SS.Prop.PropertyDetail/PropertyDetail"; p.fipsCodes = new string[] {"06059"}; ApnTools.ApnResolver apnAnalyzer = new ApnTools.ApnResolver(p, logFS, false); // // Set up a dictionary collection to hold the apns we've already resolved with the result // Dictionary apns = new Dictionary(); int count = 0; // for debugging while (count<1000 && reader.ReadNext()) { //count++; apn = reader["site_APN"]; sCurrent = "Looking up APN: " + apn; ApnResult rslt; if (!apns.ContainsKey(apn)) { rslt = apnAnalyzer.lookupApn(apn); if (rslt.Match_Type == MatchType.Parcel || rslt.Match_Type == MatchType.Property) exactMatch++; else if (rslt.Match_Type == MatchType.MultiParcel) partialMatch++; else noMatch++; apns[apn] = rslt; } else rslt = apns[apn]; sCurrent = "parsing response for APN: " + apn; // // If the apn could not be resolved to a location, locate the permit in a location in the hills // outside the city so it can be manually located using Edit Geometry. // if (rslt.Match_Type == MatchType.None) { table.Rows.Add( BuildGeo(new ApnResult(33.5355, -117.65599, 0, "", "")), // _GEOID apn, // APN "NONE", // APNMatchedTo null, // APNMatchMulti null, // APNMatchAccuracyFt // not implemented reader["permit_no"], // PermitNum ParseDate(reader["issued"]), // Issued ParseDate(reader["expired"]), // Expired reader["permittype"], // PermitType reader["status"], // PermitStatus reader["fees_charged"], // Fees null, // LocationId null, // TaxId null, // PermitOwner1 null, // PermitOwner2 null, // PermitAddress null, // PermitCityAndState null // PermitZipCode ); } else { string locId = null; if (rslt.Match_Type == MatchType.Parcel) locId = rslt.Location_Id; table.Rows.Add( BuildGeo(rslt), // _GEOID specifies source for copy geometry action rslt.Trimmed_Apn, // APN BuildMatchedTo(rslt), // APNMatchedTo BuildMatchedMulti(rslt), // APNMatchMulti rslt.Accuracy, // APNMatchAccuracyFt // Not Implemented reader["permit_no"], // PermitNum ParseDate(reader["issued"]), // Issued ParseDate(reader["expired"]), // Expired reader["permittype"], // PermitType reader["status"], // PermitStatus reader["fees_charged"], // Fees locId, // LocationId rslt.Tax_Id, // TaxId null, // PermitOwner1 null, // PermitOwner2 null, // PermitAddress null, // PermitCityAndState null // PermitZipCode ); } if (table.Rows.Count == 100) { sCurrent = "Writing DataTable"; writeRecords(@"LG8DEMO.JWILSON/PERMITS", table, version, sHosts[0], ref recseq, sCandy);// passed by ref or value? Console.WriteLine("Written up to recseq:" + recseq); } } if (table.Rows.Count > 0) writeRecords(@"LG8DEMO.JWILSON/PERMITS", table, version, sHosts[0], ref recseq, sCandy); sQueryString = String.Format("{0}version.aspx?&action=publish&versionId={2}&SS_CANDY={1}", sHosts[0], sCandy, version); oResponseXML = GetXML(sQueryString); logFS.WriteLine("Exact matches {0}, Partial Matches {1}, No Matches {2}", exactMatch, partialMatch, noMatch); logFS.Close(); } catch (Exception ex) { // TODO: if version created try to destroy the version logFS.WriteLine("APNs found {0}, APNs not found {1}", exactMatch, noMatch); logFS.Close(); Console.WriteLine(string.Format("While: {0}, Message: {1}", sCurrent, ex.Message)); } } public static void writeRecords( String layer, // resource name you are writing to eg. LG8Sprint1.gwade/WatchList DataTable table, // data you want to write – column names and type same as destination table String version, // versionId that you get back from version.aspx String sHost, // URL to use for transactions – returned by InitSession ref int recseq, // input and output – initially set to start reseq and incremented here String sAuth) // SS_CANDY returned by InitSession { // // // // // // // // /// /// string sCurrent; XElement Dom = new XElement("Request"); Dom.Add(new XAttribute("versionId", version)); XElement iDom = new XElement("Insert"); iDom.Add(new XAttribute("resource", layer)); Dom.Add(iDom); XElement Geolink; XElement Row; foreach (DataRow r in table.Rows) { //Create Seed Row record Row = new XElement("Row"); Row.Add(new XAttribute("_RECSEQ", recseq)); iDom.Add(Row); // for each attribute set XML attribute, if GeoID set GetByKey sCurrent = "Constructing Transaction XML for record " + recseq; foreach (DataColumn fld in table.Columns) { if (fld.ColumnName == @"_GEOID") { /// add Geolink - this allows the geometry to be copied to this record from another source /// must be column called _GEOID in the data table with value in the format || /// for example SS.Prop.ParcelDetail/ParcelDetail|_DMP_ID|3531_1204472243 will copy the parcel geometry to the new record string s = r[fld.Ordinal].ToString(); if (!String.IsNullOrEmpty(s)) { if (s[0].Equals('*')) { Geolink = new XElement("GetByKey"); Geolink.Add(new XAttribute("Name", "R1")); String[] src = s.Split('|'); if (src.Length != 3) { throw new ApplicationException(String.Format("Invalid geolink {0} at record '{1}'", s, recseq)); } Geolink.Add(new XAttribute("Datasource", src[0].Substring(1))); // skip the "*" beginning the string Geolink.Add(new XAttribute("KeyFields", src[1])); Geolink.Add(new XAttribute("KeyValues", src[2])); Geolink.Add(new XAttribute("Fields", "GEOMETRY")); Geolink.Add(new XAttribute("Host", "dc1.parcelstream.com")); Geolink.Add(new XAttribute("ReturnGeoType", "3")); Row.Add(Geolink); Row.Add(new XAttribute("GEOMETRY", "{R1.GEOMETRY}")); } else if (s[0].Equals('+')) { Row.Add(new XAttribute("GEOMETRY", s.Substring(1))); // skip the "+" beginning the string } else throw new ApplicationException(String.Format("Invalid GEOID {0} at record '{1}'", s, recseq)); } } else { Row.Add(new XAttribute(fld.ColumnName, r[fld.Ordinal].ToString())); } } recseq++; } sCurrent = "Posting transaction batch for up to record number " + recseq; HttpWebRequest Req = (HttpWebRequest)WebRequest.Create(sHost + "Transaction.aspx"); Req.Method = "POST"; String postData = String.Format("SS_CANDY={0}&xml={1}&_dmp_debug=true", sAuth, HttpUtility.UrlEncode(Dom.ToString())); byte[] byteArray = Encoding.UTF8.GetBytes(postData); Req.ContentType = "application/x-www-form-urlencoded"; Req.ContentLength = byteArray.Length; Stream dataStream = Req.GetRequestStream(); dataStream.Write(byteArray, 0, byteArray.Length); dataStream.Close(); WebResponse oResponse = Req.GetResponse(); XmlDocument oResponseXML = new XmlDocument(); oResponseXML.Load(oResponse.GetResponseStream()); // report load results here XmlNode oNode = oResponseXML.SelectSingleNode("Response/Insert"); if (oNode == null) { throw new ApplicationException(sCurrent + ":" + oResponseXML.SelectSingleNode("Response/Error/@message").Value); } oResponse.Close(); table.Clear(); // empty the table } } }