I'm looking to create a db model of various units and their relation to each other. For instance, 36 inches = 3 feet = 1 yard = .9144 meters etc. This table would also store cups in ounces, pounds, kg, grams, cm and all sorts of measurements.
How do you do this? I was thinking about something like this:
Amount | Units | ConversionFactor | ConversionUnits
1 | foot | 12 | inches
1 | yard | 36 | inches
But frankly, this seems like a terrible idea. Trying to figure out how many feet in a yard would be very convoluted, and I don't think I could ever store all the conversions I need.
What other ideas are there? I know this is a solved problem. Thanks!
Store conversions to SI units, not to other non-metric units. Then you can convert between units in without needing to know the explicit conversion.
Unit | Class | Base Unit Multiplier
------------------------------------------------------
foot | length | 0.304800610
yard | length | 0.914401830
square foot | area | 0.092903040
...
So 14 feet in yards is:
14 feet * 0.304800610 = 4.26720854 meters
4.26720854 meters * 0.914401830⁻¹ = 4.66666667 yards
Pick a base unit for each dimension you are interested in (read that wiki page, it'll be useful). For example, if most of your data is in SI units, you would pick kilogram for mass, second for time, metre for distance, and so on. If most of your data is in US units, pick units from the US customary units, for example pound for mass, foot for length, second for time.
Then store, for each actual unit you want to be able to handle, the conversion factor to the dimensionally-appropriate base unit. So if you choose foot as your base unit of distance, store
Unit Dimension Factor
Foot Distance 1
Metre Distance 3.28084
Mile Distance 5280
To actually do a conversion, once you've checked that the dimensions match, simply multiply by the Factor of the source unit, and divide by the Factor of the destination unit. For example, to get from metres to miles, multiply by 3.28084, then divide by 5280.
CREATE TABLE UnitConversion
(
[FromUnit] NVARCHAR(100),
[ToUnit] NVARCHAR(100),
[FromOffset] DECIMAL(29,10),
[Multiplicand] DECIMAL(29,10),
[Denominator] DECIMAL(29,10),
[ToOffset] DECIMAL(29,10)
)
ToUnit = (FromUnit + FromOffset) * Multiplicand / Denominator + ToOffset
I think the original post's proposed schema is fine, apart from not including Class (as in Seth's answer) - you don't want to try to convert between pints and inches.
Converting between two units where neither of them is the conversion unit is simply achieved by retrieving both units' conversion records and dividing one factor by the other (eg. 36/12 = 3 feet in a yard).
If you are particularly concerned about accuracy, you could ensure that all units for a given class have entries for all other units in the same class - this strikes me as overkill, though.
Related
So my data in the table looks like this:
amount | ID
10918.6 | ABC
9999.99 | BCD
9999.89 | DEF
I need to find all consecutive digit (9999.99, 1111.11, 2222.22 etc except 0000.00) So from above example output should give only BCD. I have to check for 1k place only.
If I have 9999.99 and 99.99 it should only give me 9999.99.
Also if I have 989999.99 I have to consider this also as my accepted output
I can do this by using where clause -- column like '%9999.99' or '%1111.11' but I need to find the better way may be by regular exp etc.
Using modulo you can strip away any digits above the 10k position, then check the values are in an accepted list.
WHERE
(amount % 10000) IN (1111.11, 2222.22, 3333.33, 4444.44, 5555.55, 6666.66, 7777.77, 8888.88, 9999.99)
Or...
WHERE
(amount % 10000) / 1111.11 IN (1,2,3,4,5,6,7,8,9)
These avoid turning numbers in to strings, which is generally neither necessary nor prudent.
I have an array of records (custom data type) in Haskell which I want to aggregate based on a each records' timestamp. In very general terms each record looks like this:
data Record = Record { event :: String,
time :: Double,
from :: Int,
to :: Int
} deriving (Show, Eq)
I used a Double for the timestamp since that is the same format used in the tracefile.
And I parse them from a CSV file into an array of records: [Record]
Now I'm looking to get an approximation of instantaneous events / time. So I want to split the array into several arrays based on the timestamp (say. every 1 seconds) and then fold across each smaller array.
The problem is I can't figure out how to split an array based on the value of a record. Looking on Hoogle I found several functions like splitEvery and splitWhen, but I'm lost. I considered using splitWhen to break up the list when, say, (mod time 0.1) == 0, but even if that worked it would remove the elements it's splitting on (which I don't want to do).
I should note that the records are NOT evenly spaced in time. E.g. the timestamp on sequential records is not going to differ by a fixed amount.
I am more than willing to store the data in a different format if you can suggest one that would make this sort of work easier.
A quick sample of the data I'm parsing (from a ns2 simulation):
r 0.114 1 2 tcp 1000 ________ 2 1.0 5.0 0 2
r 0.240 1 2 tcp 1000 ________ 2 1.0 5.0 0 2
r 0.914 2 1 tcp 1000 ________ 2 5.0 1.0 0 3
If you have [Record] and you want to group them by a specific condition, you can use Data.List.groupBy. I'm assuming that for your time :: Double, 1 second is the base unit, so time = 1 is 1 second, time = 100 is 100 seconds, etc, so adjust this to whatever system you're actually using:
import Data.List
import Data.Function (on)
isInSameClockSecond :: Record -> Record -> Bool
isInSameClockSecond = (==) `on` (floor . time :: Record -> Integer)
-- The type signature is given for floor . time to remove any ambiguity
-- due to floor's polymorphic type signature.
groupBySameClockSecond :: [Record] -> [[Record]]
groupBySameClockSecond = groupBy isInSameClockSecond
I have run into a problem using PostGIS ST_DWithin using geometry points that has me completely stumped. Even though I'm using geometry points with SRID 3857 (see below), ST_DWithin interprets seems to interpret the third argument ( double precision distance_of_srid ) as DEGREES. Here's an example.
Using this table test_person_avg_lngs_lats:
Column | Type | Modifiers
---------------------+----------------------+-----------
avg_lng | double precision |
avg_lat | double precision |
person_avg_location | geometry(Point,3857) |
store_lng | double precision |
store_lat | double precision |
store_location | geometry(Point,3857) |
and the following query:
SELECT avg_lat, avg_lng, store_lng, store_lat,
ST_Distance_Spheroid(person_avg_location, store_location, CAST('SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG","7030\"]]' AS spheroid))/1000 AS distance,
ST_DWithin(person_avg_location, store_location, 1) AS dwithin
FROM test_person_avg_lngs_lats
WHERE ST_DWithin(person_avg_location, store_location, 1)
The query returns results that interpret the third argument to ST_DWithin as 1 DEGREE versus 1 METER, even though I'm using geometry points with SRID 3857 which I have confirmed, uses meters units. Whatever N I pass as the third argument to ST_DWithin, the results consistently return distances around N * 100 km ( ~ 66 miles ). That's why I'm assuming ST_DWithin is interpreting it as 1 DEGREE.
Here is a sample result that should be interpreted as one meter (distance is in miles):
avg_lat avg_lng store_lng store_lat distance dwithin
43.3275959623, -71.1169553872, -71.0626, 42.3291, 68.9794023576, true
This is the closest I've come to something on the subject: ST_DWithin takes parameter as degree , not meters , why?
Any ideas as to what might be causing this, or what I might look for to move forward in analyzing the problem?
I'm using:
postgis_full_version
------------------------------------------------------------------
POSTGIS="2.0.0 r9605" GEOS="3.3.3-CAPI-1.7.4" PROJ="Rel. 4.8.0, 6 March 2012" GDAL="GDAL 1.9.2, released 2012/10/08" LIBXML="2.7.8" LIBJSON="UNKNOWN" RASTER
(1 row)
version
-------------------------------------------------------------
PostgreSQL 9.1.9 on x86_64-unknown-linux-gnu, compiled by gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3, 64-bit
(1 row)
Obviously, distance_of_srid uses the same distance units of the SRID, which is typically either degrees or meters. With the geometry type, distances are calculated on a flat Cartesian plane using maths familiar to most high school students. The units are not interpreted for the geometry type.
However, this assumes that the data are actually projected with the correct SRID. If you mix a projected SRID like 3857 with lat/long coordinates expressed as degrees than you will get unexplainable garbage. Review how you populated the person_avg_location and store_location columns, because I'm 99.9% certain that there was an error there.
I would like to ask how to create a circle with radius=4km. I have tried the ST_Buffer function but it creates a larger circle. (I see the created circle by inserting its polygon into an new kml file.)
This is what i am trying.
INSERT INTO camera(geom_circle) VALUES(geometry(ST_Buffer(georgaphy(ST_GeomFromText('POINT(21.304116745663165 38.68607570952619)')), 4000)))
The center of the circle is a lon lat point but I don't know its SRID because I have imported it from a kml file.
Do I need the SRID in order to transform the geometries etc?
KML files are always lat/long and use SRID=4326. This SRID is implied if you use geography. Geography is a good way to mix-in the 4 km metric measure on lat/long data ... excellent you tried this!
Try this statement to fix up the casts, and use a parameterized point constructor:
SELECT ST_Buffer(ST_MakePoint(21.304116745663165, 38.68607570952619)::geography, 4000);
And if you need to cast this back to geometry, add a ::geometry cast to the end.
Update on accuracy
The previous answer internally re-projects the geometry (usually) to a UTM zone that the point fits within (see ST_Buffer). This may cause minor distortions if the point is on the edge of two UTM boundaries. Most folks won't care about the size of these errors, but it will often be several meters. However, if you require sub millimeter precision, consider building a dynamic azimuthal equidistant projection. This requires PostGIS 2.3's ST_Transform, and is adapted from another answer:
CREATE OR REPLACE FUNCTION geodesic_buffer(geom geometry, dist double precision,
num_seg_quarter_circle integer)
RETURNS geometry AS $$
SELECT ST_Transform(
ST_Buffer(ST_Point(0, 0), $2, $3),
('+proj=aeqd +x_0=0 +y_0=0 +lat_0='
|| ST_Y(ST_Centroid($1))::text || ' +lon_0=' || ST_X(ST_Centroid($1))::text),
ST_SRID($1))
$$ LANGUAGE sql IMMUTABLE STRICT COST 100;
CREATE OR REPLACE FUNCTION geodesic_buffer(geom geometry, dist double precision)
RETURNS geometry AS 'SELECT geodesic_buffer($1, $2, 8)'
LANGUAGE sql IMMUTABLE STRICT COST 100;
-- Optional warppers for geography type
CREATE OR REPLACE FUNCTION geodesic_buffer(geog geography, dist double precision)
RETURNS geography AS 'SELECT geodesic_buffer($1::geometry, $2)::geography'
LANGUAGE sql IMMUTABLE STRICT COST 100;
CREATE OR REPLACE FUNCTION geodesic_buffer(geog geography, dist double precision,
num_seg_quarter_circle integer)
RETURNS geography AS 'SELECT geodesic_buffer($1::geometry, $2, $3)::geography'
LANGUAGE sql IMMUTABLE STRICT COST 100;
A simple example to run one of the functions is:
SELECT geodesic_buffer(ST_MakePoint(21.304116745663165, 38.68607570952619)::geography, 4000);
And to compare the distances to each of the buffered points, here are the lengths of each geodesic (shortest path on an ellipsoid of revolution, i.e. WGS84). First this function:
SELECT count(*), min(buff_dist), avg(buff_dist), max(buff_dist)
FROM (
SELECT ST_Distance((ST_DumpPoints(geodesic_buffer(poi, dist)::geometry)).geom, poi) AS buff_dist
FROM (SELECT ST_MakePoint(21.304116745663165, 38.68607570952619)::geography AS poi, 4000 AS dist) AS f
) AS f;
count | min | avg | max
-------+----------------+-----------------+----------------
33 | 3999.999999953 | 3999.9999999743 | 4000.000000001
Compare this to ST_Buffer (first part of answer), that shows it's off by about 1.56 m:
SELECT count(*), min(buff_dist), avg(buff_dist), max(buff_dist)
FROM (
SELECT ST_Distance((ST_DumpPoints(ST_Buffer(poi, dist)::geometry)).geom, poi) AS buff_dist
FROM (SELECT ST_MakePoint(21.304116745663165, 38.68607570952619)::geography AS poi, 4000 AS dist) AS f
) AS f;
count | min | avg | max
-------+----------------+------------------+----------------
33 | 4001.560675049 | 4001.56585986067 | 4001.571105793
I have a list of values such as "12000","12345","123456" that need to be converted to currency ("120.00", "123.45", "1234.56"). The only way I know is to convert the value to a string, copy the first strlen()-2 characters to one string (dollars) and the remainging two digits to another string(cents) and then write them as the following:
printf("%s.%s", dollars, cents);
printf("$%.2f", value/100);
Don't use floats for storing or representing monetary amounts. Use longs (if you need more than 4 billion cent use llongs). Its usually a good idea to represent currency in its minimum usable unit, example use 10000 to represent 100Euro). Then the correct way to format these values (assuming 100 cent to the euro or dollar) is:
printf( "%d.%02d", value/100, value%100);
Hope that makes sense...
Calculations with currency values is a complex subject but you cant go far wrong is you always aim to have a rounded answer to the nearest currency unit (cent for example) and always make sure that rounding errors are calculated for (example, to divide 1 dollar three ways you should end up with 33+33+34 or 33+33+33+1).
to prefix values less than $1.00 with 0, use:
printf( "$%0.2f", value / 100.0 );
This will result in $0.25 if value = 25