How to convert GPS exif to geography? - sql-server

I have attachments table which has GPSLatitude and GPSLongitude columns for each attachment. It's legacy code which is populating the fields and the values looks like:
GPSLatitude
50/1,5/1,1897/100
GPSLongitude
14/1,25/1,4221/100
Is there any build in function which I can use in order to convert them to latitude and longitude decimal values like this:
Location Latitude
41.5803
Location Longitude
-83.9124
I can implement SQL CLR function if this can be done easier with .net also.
What is most difficult for me right now is to understand what these values represent. The legacy code is using some API with no documentation about the returned format and how to read it.
The values above are just for showing how the data is formatted. The following library is used to get the values - chestysoft like this:
IF Image.ExifValueByName("GPSLatitude") <> "" THEN GPSLatitude = Image.ExifValueByName("GPSLatitude") ELSE GPSLatitude = NULL END IF
IF Image.ExifValueByName("GPSLongitude") <> "" THEN GPSLongitude = Image.ExifValueByName("GPSLongitude") ELSE GPSLongitude = NULL END IF

I'm fairly certain you should read it as:
50/1: 50 Degrees
5/1: 5 Minutes
1897/100: 18.97 Seconds
This would put the location you've given in New York (assuming N/W), does that make sense? If you have no way to validate the output it's very difficult to make any other suggestion... See also here
In the link you provided, you can upload a picture to view the exif data. There you can test with known locations. It is also apparent that in the values you mentioned, the GPSLatitudeRef and GPSLongitudeRef are missing. You need these to change the values to a location. Do you have those values in your table? Otherwise you'll have to make (wild) assumptions.
This is by the way the standard EXIF notation for latitude/longitude; I assume there are many, many tools to convert it.

Assuming that #Cool_Br33ze is correct, and the data is in degrees, minutes and seconds, you can calculate the values you need using the following:
declare #v varchar(30) = '50/1,5/1,1897/100'
select #v Original_Value,
convert(decimal(18,4), left(#v, charindex('/', #v) - 1)) [Degrees],
convert(decimal(18,4), substring(
#v,
charindex(',', #v) + 1,
charindex('/', #v, charindex(',', #v)) - (charindex(',', #v) + 1)
) / 60.0
) [Minutes],
convert(decimal(18,4), substring(
#v,
charindex(',', #v, (charindex(',', #v) + 1)) + 1,
charindex('/', #v, charindex(',', #v, (charindex(',', #v) + 1))) - (charindex(',', #v, (charindex(',', #v) + 1)) + 1)
) / 360000.0
) [Seconds]
It looks a bit of a mess, but it splits out the degrees, minutes and seconds (converted to DECIMAL(18,4)), all you need to do is add the three values together to get your Lat/Long value in degrees.
I'd test it thoroughly before implementing it though.

Related

Using LEFT() and SUBSTRING() to pull only so many characters from string

I'm trying to slice out an exact portion of text from a very long XML field in my database - I only want the text starting at the word "General_Notes" and ending at "General_Notes_Conv" - I used the advice at this post here to work this line into my query:
LEFT(SUBSTRING(PCO.Cycle_Order_Xml,
CHARINDEX('General_Notes', PCO.Cycle_Order_Xml), 500),
CHARINDEX('General_Notes_Conv', PCO.Cycle_Order_Xml))
Which works, except right now it's pulling 500 characters worth of text starting at "General Notes" - I was under the impression that the second CHARINDEX indicates that I'm only pulling text until that word appears - I'm not sure what purpose that second CHARINDEX has at all, but I'd like this to be dynamic and only pull from General Notes to General_Notes_Conv - what should I change?
Substring works like SUBSTRING(input_string, start, length)
Left work like LEFT ( input_string , number_of_characters ).
I think for your purpose you just need SUBSTRING.
You can find length by subtracting position of 'General_Notes' from position of 'General_Notes_Conv' and adjusting for the length of the beginning tag (which is 'General_Notes') as index gives beginning of a string.
SUBSTRING(column_name, CHARINDEX('General_Notes', column_name) + LEN('General_Notes'), CHARINDEX('General_Notes_Conv', column_name) - CHARINDEX('General_Notes', column_name))
CHARINDEX() gets you the character position specified. You need to find the difference
DECLARE #String VARCHAR(200) = 'Some text you don''t care about General_Notes this is the data i want General_Notes_Conv more stuff I don''t care about'
SELECT LTRIM(RTRIM(SUBSTRING(#String, CHARINDEX('General_Notes', #String),
CHARINDEX('General_Notes_Conv', #String)
- CHARINDEX('General_Notes', #String))))
To get only the data between, just a little addition.
DECLARE #String VARCHAR(200) = 'Some text you don''t care about General_Notes this is the data i want General_Notes_Conv more stuff I don''t care about'
SELECT LTRIM(RTRIM(SUBSTRING(#String, CHARINDEX('General_Notes', #String) + 13,
CHARINDEX('General_Notes_Conv', #String)
- CHARINDEX('General_Notes', #String))))
To get both tags included as well:
DECLARE #String VARCHAR(200) = 'Some text you don''t care about <General_Notes> this is the data i want <General_Notes_Conv> more stuff I don''t care about'
SELECT LTRIM(RTRIM(SUBSTRING(#String, CHARINDEX('<General_Notes>', #String),
CHARINDEX('<General_Notes_Conv>', #String)
- CHARINDEX('<General_Notes>', #String) + 19)))

base64 encode for chinese chars

I'm using both the following methods to encode in base 64 a Chinese string. Problem is that I'm having Pz8= as output, which decoded is ??
What's wrong with this and how can I fix it?
Method 1
CREATE FUNCTION [dbo].[base64Encode] (#input VARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #output NVARCHAR(MAX),
#bits VARBINARY(3),
#pos INT
SET #pos = 1
SET #output = ''
WHILE #pos <= LEN(#input)
BEGIN
SET #bits = CONVERT(VARBINARY(3), SUBSTRING(#input, #pos, 3))
SET #output = #output + SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', SUBSTRING(#bits, 1, 1) / 4 + 1, 1)
SET #output = #output + SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', SUBSTRING(#bits, 1, 1) % 4 * 16 + SUBSTRING(#bits, 2, 1) / 16 + 1, 1)
SET #output = #output + SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', SUBSTRING(#bits, 2, 1) % 16 * 4 + SUBSTRING(#bits, 3, 1) / 64 + 1, 1)
SET #output = #output + SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', SUBSTRING(#bits, 3, 1) % 64 + 1, 1)
SET #pos = #pos + 3
END
RETURN (LEFT(#output, LEN(#output) - 3 + LEN(#bits)) + REPLICATE('=', 3 - LEN(#bits)))
END
SELECT [dbo].[base64Encode]('你好')
Method 2
SELECT CAST('你好' as varbinary(max)) FOR XML PATH(''), BINARY BASE64
You are missing the N to mark a string literal as unicode:
SELECT N'你好' AS unicode
,'你好' AS ASCII
Try this to get a base64 out of your chinese charcters and vice versa:
SELECT (SELECT CAST(N'你好' AS VARBINARY(MAX)) FOR XML PATH(''),TYPE).value(N'.','nvarchar(max)');
You get this base64 result: YE99WQ==
This is the way to re-convert the base64 to the original value
SELECT CAST(CAST('<x>' + 'YE99WQ==' + '</x>' AS XML).value('.','varbinary(max)') AS NVARCHAR(MAX));
UPDATE Some words about the re-encoding
base64 does not encode a string value, but the binary pattern a system uses to keep that string in memory (this is valid for any data type actually). The bit pattern of a string differs with UTF-8, UTF-16, ASCII whatever... And even worse there is BE and LE.
The steps to get base64 are:
Get the bit pattern of my value (a string, a date, a picture, any value actually)
compute the base64 for this bit pattern
The steps for the re-encoding are
Compute the original bit pattern which is hidden within the base64
Interpret the bit pattern as the original value
The very last step might bring up confusion... You have to know exactly which binary representation a system uses. You have to use exactly the same data type with exactly the same interpretation to get the values back.
With strings one has to know, that SQL-Server works with a very limited choice natively.
There is NVARCHAR (NCHAR), which is 2-byte encoded unicode in UCS-2 flavour (almost the same as utf-16)
And there is VARCHAR (CHAR), which is 1-byte encoded extended ASCII. All non-latin characters are bound to a code page within the connected collation. But this is not UTF-8!

Modify my variable

I need to modify/cut some characters from my variable #txt
The variable is declared like this:
DECLARE #txt VARCHAR(MAX)
and #txt is a long string.
I need to cut and move this marked fragment in blue (screenshot)
How can I do this? Could you please give me some advice that will help me?
enter image description here
Use your data cleverly. We assume that values in 1st and 2nd fields (eg Corp and 111EF111) are variable length.
Therefore, we search for the - in the date, and then for the 3rd comma after it:
SELECT CHARINDEX('-', #txt) + 15 AS Index_First_Date_Dash
,CHARINDEX(',',#txt, CHARINDEX('-', #txt) + 15 + 12) AS Index_3rd_Comma_After_Time
,SUBSTRING(#txt,1,CHARINDEX(',',#txt, CHARINDEX('-', #txt) + 15 + 12)) AS Extract

How to extract this specific substring in SQL Server?

I have a string with a specific pattern:
23;chair,red [$3]
i.e., a number followed by a semicolon, then a name followed by a left square bracket.
Assuming the semicolon ; always exists and the left square bracket [ always exists in the string, how do I extract the text between (and not including) the ; and the [ in a SQL Server query? Thanks.
Combine the SUBSTRING(), LEFT(), and CHARINDEX() functions.
SELECT LEFT(SUBSTRING(YOUR_FIELD,
CHARINDEX(';', YOUR_FIELD) + 1, 100),
CHARINDEX('[', YOUR_FIELD) - 1)
FROM YOUR_TABLE;
This assumes your field length will never exceed 100, but you can make it smarter to account for that if necessary by employing the LEN() function. I didn't bother since there's enough going on in there already, and I don't have an instance to test against, so I'm just eyeballing my parentheses, etc.
Assuming they always exist and are not part of your data, this will work:
declare #string varchar(8000) = '23;chair,red [$3]'
select substring(#string, charindex(';', #string) + 1, charindex(' [', #string) - charindex(';', #string) - 1)
An alternative to the answer provided by #Marc
SELECT SUBSTRING(LEFT(YOUR_FIELD, CHARINDEX('[', YOUR_FIELD) - 1), CHARINDEX(';', YOUR_FIELD) + 1, 100)
FROM YOUR_TABLE
WHERE CHARINDEX('[', YOUR_FIELD) > 0 AND
CHARINDEX(';', YOUR_FIELD) > 0;
This makes sure the delimiters exist, and solves an issue with the currently accepted answer where doing the LEFT last is working with the position of the last delimiter in the original string, rather than the revised substring.
select substring(your_field, CHARINDEX(';',your_field)+1
,CHARINDEX('[',your_field)-CHARINDEX(';',your_field)-1)
from your_table
Can't get the others to work. I believe you just want what is in between ';' and '[' in all cases regardless of how long the string in between is. After specifying the field in the substring function, the second argument is the starting location of what you will extract. That is, where the ';' is + 1 (fourth position - the c), because you don't want to include ';'. The next argument takes the location of the '[' (position 14) and subtracts the location of the spot after the ';' (fourth position - this is why I now subtract 1 in the query). This basically says substring(field,location I want substring to begin, how long I want substring to be). I've used this same function in other cases. If some of the fields don't have ';' and '[', you'll want to filter those out in the "where" clause, but that's a little different than the question. If your ';' was say... ';;;', you would use 3 instead of 1 in the example. Hope this helps!
If you need to split something into 3 pieces, such as an email address and you don't know the length of the middle part, try this (I just ran this on sqlserver 2012 so I know it works):
SELECT top 2000
emailaddr_ as email,
SUBSTRING(emailaddr_, 1,CHARINDEX('#',emailaddr_) -1) as username,
SUBSTRING(emailaddr_, CHARINDEX('#',emailaddr_)+1, (LEN(emailaddr_) - charindex('#',emailaddr_) - charindex('.',reverse(emailaddr_)) )) domain
FROM
emailTable
WHERE
charindex('#',emailaddr_)>0
AND
charindex('.',emailaddr_)>0;
GO
Hope this helps.

Convert float into varchar in SQL Server without scientific notation

Convert float into varchar in SQL Server without scientific notation and trimming decimals.
For example:
I have the float value 1000.2324422, and then it would be converted into varchar as same 1000.2324422.
There could be any number of decimal values...the float value comes randomly.
Casting or converting to VARCHAR(MAX) or anything else did not work for me using large integers (in float fields) such as 167382981, which always came out '1.67383e+008'.
What did work was STR().
Neither str() or cast(float as nvarchar(18)) worked for me.
What did end up working was converting to an int and then converting to an nvarchar like so:
convert(nvarchar(18),convert(bigint,float))
The STR function works nice. I had a float coming back after doing some calculations and needed to change to VARCHAR, but I was getting scientific notation randomly as well. I made this transformation after all the calculations:
ltrim(rtrim(str(someField)))
Try CAST(CAST(#value AS bigint) AS varchar)
This works:
CONVERT(VARCHAR(100), CONVERT(DECIMAL(30, 15), fieldname))
Try this:
SELECT REPLACE(RTRIM(REPLACE(REPLACE(RTRIM(REPLACE(CAST(CAST(YOUR_FLOAT_COLUMN_NAME AS DECIMAL(18,9)) AS VARCHAR(20)),'0',' ')),' ','0'),'.',' ')),' ','.') FROM YOUR_TABLE_NAME
Casting as DECIMAL will put decimal point on every value, whether it
had one before or not.
Casting as VARCHAR allows you to use the REPLACE function
First REPLACE zeros with spaces, then RTRIM to get rid of all trailing spaces (formerly zeros), then REPLACE remaining spaces with zeros.
Then do the same for the period to get rid of it for numbers with no decimal values.
This is not relevant to this particular case because of the decimals, but may help people who google the heading.
Integer fields convert fine to varchars, but floats change to scientific notation. A very quick way to change a float quickly if you do not have decimals is therefore to change the field first to an integer and then change it to a varchar.
Below is an example where we can convert float value without any scientific notation.
DECLARE #Floater AS FLOAT = 100000003.141592653
SELECT CAST(ROUND(#Floater, 0) AS VARCHAR(30))
,CONVERT(VARCHAR(100), ROUND(#Floater, 0))
,STR(#Floater)
,LEFT(FORMAT(#Floater, ''), CHARINDEX('.', FORMAT(#Floater, '')) - 1)
SET #Floater = #Floater * 10
SELECT CAST(ROUND(#Floater, 0) AS VARCHAR(30))
,CONVERT(VARCHAR(100), ROUND(#Floater, 0))
,STR(#Floater)
,LEFT(FORMAT(#Floater, ''), CHARINDEX('.', FORMAT(#Floater, '')) - 1)
SET #Floater = #Floater * 100
SELECT CAST(ROUND(#Floater, 0) AS VARCHAR(30))
,CONVERT(VARCHAR(100), ROUND(#Floater, 0))
,STR(#Floater)
,LEFT(FORMAT(#Floater, ''), CHARINDEX('.', FORMAT(#Floater, '')) - 1)
SELECT LEFT(FORMAT(#Floater, ''), CHARINDEX('.', FORMAT(#Floater, '')) - 1)
,FORMAT(#Floater, '')
In the above example, we can see that the format function is useful for us. FORMAT() function returns always nvarchar.
I have another solution since the STR() function would result some blank spaces, so I use the FORMAT() function as folowing example:
SELECT ':' + STR(1000.2324422), ':' + FORMAT(1000.2324422,'##.#######'), ':' + FORMAT(1000.2324422,'##')
The result of above code would be:
: 1000 :1000.2324422 :1000
You can use this code:
STR(<Your Field>, Length, Scale)
Your field = Float field for convert
Length = Total length of your float number with Decimal point
Scale = Number of length after decimal point
For example:
SELECT STR(1234.5678912,8,3)
The result is: 1234.568
Note that the last digit is also round up.
You will have to test your data VERY well. This can get messy. Here is an example of results simply by multiplying the value by 10. Run this to see what happens.
On my SQL Server 2017 box, at the 3rd query I get a bunch of *********. If you CAST as BIGINT it should work every time. But if you don't and don't test enough data you could run into problems later on, so don't get sucked into thinking it will work on all of your data unless you test the maximum expected value.
Declare #Floater AS FLOAT =100000003.141592653
SELECT CAST(ROUND(#Floater,0) AS VARCHAR(30) ),
CONVERT(VARCHAR(100),ROUND(#Floater,0)),
STR(#Floater)
SET #Floater =#Floater *10
SELECT CAST(ROUND(#Floater,0) AS VARCHAR(30) ),
CONVERT(VARCHAR(100),ROUND(#Floater,0)),
STR(#Floater)
SET #Floater =#Floater *100
SELECT CAST(ROUND(#Floater,0) AS VARCHAR(30) ),
CONVERT(VARCHAR(100),ROUND(#Floater,0)),
STR(#Floater)
There are quite a few answers but none of them was complete enough to accommodate the scenario of converting FLOAT into NVARCHAR, so here we are.
This is what we ended up with:
DECLARE #f1 FLOAT = 4000000
DECLARE #f2 FLOAT = 4000000.43
SELECT TRIM('.' FROM TRIM(' 0' FROM STR(#f1, 30, 2))),
TRIM('.' FROM TRIM(' 0' FROM STR(#f2, 30, 2)))
SELECT CAST(#f1 AS NVARCHAR),
CAST(#f2 AS NVARCHAR)
Output:
------------------------------ ------------------------------
4000000 4000000.43
(1 row affected)
------------------------------ ------------------------------
4e+006 4e+006
(1 row affected)
In our scenario the FLOAT was a dollar amount to 2 decimal point was sufficient, but you can easily increase it to your needs.
In addition, we needed to trim ".00" for round numbers.
Try this code
SELECT CONVERT(varchar(max), CAST(1000.2324422 AS decimal(11,2)))
Result:
1000.23
Here decimal(11,2): 11-total digits count (without the decimal point), 2 - for two digits after the decimal point
None of the previous answers for me. In the end I simply used this:
INSERT INTO [Destination_Table_Name]([Field_Name])
SELECT CONCAT('#',CAST([Field_Name] AS decimal(38,0))) [Field_Name]
FROM [dbo].[Source_Table_Name] WHERE ISNUMERIC([CIRCUIT_NUMBER]) = 1
INSERT INTO [Destination_Table_Name]([Field_Name])
SELECT [Field_Name]
FROM [dbo].[Source_Table_Name] WHERE ISNUMERIC([CIRCUIT_NUMBER]) <> 1
select format(convert(float,#your_column),'0.0#########')
Advantage: This solution is independent of the source datatype (float, scientific, varchar, date, etc.)
String is limited to 10 digits, and bigInt gets rid of decimal values.
This works:
Suppose
dbo.AsDesignedBites.XN1E1 = 4016519.564`
For the following string:
'POLYGON(('+STR(dbo.AsDesignedBites.XN1E1, 11, 3)+'...

Resources