|
|
Recently I wrote a cheat sheet for pinvoking in .NET. Shortly after I got a question in comments about how to deal with variable arguments, when it's more, then one parameter. Also what to do if those arguments are heterogeneous? Let's say, that we have following method in C: int VarSum(int nargs, ...){ va_list argp; va_start( argp, nargs ); int sum = 0; for( int i = 0 ; i < nargs; i++ ) { int arg = va_arg( argp, int ); sum += arg; } va_end( argp ); return sum; } We can expose this method to C# as following: [System.Runtime.InteropServices.DllImportAttribute("unmanaged.dll", EntryPoint = "VarSum")] public static extern int VarSum(int nargs,int arg1); [System.Runtime.InteropServices.DllImportAttribute("unmanaged.dll", EntryPoint = "VarSum")] public static extern int VarSum(int nargs,int arg1,int arg2); [System.Runtime.InteropServices.DllImportAttribute("unmanaged.dll", EntryPoint = "VarSum")] public static extern int VarSum(int nargs,int arg1,int arg2,int arg3); etc... And it will work. However, if you'll try to expose it as int array, marshaller will fail to understand how to align things [System.Runtime.InteropServices.DllImportAttribute("unmanaged.dll", EntryPoint = "VarSum")] public static extern int VarSum(int nargs,int[] arg); This in spite of the fact, that this method will work properly with another signature int ArrSum(int* nargs) { int sum = 0; for( int i = 0 ; i < 2; i++ ) { sum += nargs[i]; } return sum; } So what to do? The official answer is - you have nothing to do, rather then override all possibilities. This is very bad and absolutely not flexible. So, there is small class in C#, named ArgIterator. This one is similar to params object[], but knows to marshal into varargs. The problem is, that you have no way to add things inside. It's "kind-of-read-only". Let's look into reflected version of ArgIterator. We'll see there something, named __arglist and __refvalue. OMG, isn't it good old stuff similar to "__declspec(dllexport) int _stdcall" etc.? It is! But can we use it in C#? We can! Just sign your method as Cdecl and you have working signature for "..." [System.Runtime.InteropServices.DllImportAttribute("unmanaged.dll", EntryPoint = "VarSum", CallingConvention=System.Runtime.InteropServices.CallingConvention.Cdecl)] public static extern int VarSum(int nargs, __arglist); Yes, looks strange, and absolutely not CLR compliant. However, this is the only way to expose varargs to CLR via P/Invoke. How to use it? Simple: c = VarSum(2, __arglist(5, 10)); Have a nice day and be good people. Also, my question to Microsoft is why this stuff is not in MSDN and we, as developers, have no way to get rid of it. Is not it very good practices to use non-compliant methods? Give us another way to do it! Is not it very good practices to use variable arguments in unmanaged method signatures? So why you want dynamic types in C# 4? Source code for this article As you, probably, know, I left consulting field. However, it does not mean, that I quit helping developers community with client application development. Also, every day I'm getting between 50 and 300 emails with questions (I'm trying to answer all of those) and sometimes proposals for consulting. Currently I'm refusing all those, because I do not want to engage to it. However, there are too much people, who really need professional developers help and there are very few good development consultants in our area. Thus I decided to keep consulting, but this time only for charity. How does it work? - You want me to help you with your development.
- I have free time for it.
- We decide together about the fee.
- You get consulting and you are happy with it.
- I tell you what charity organization to transfer all amount, you should pay (except TBL, if there are).
- You transfer it.
- We made the world a bit better!
To clarify things: - It's not charity foundation - you will transfer the money directly to organization, that need it
- I'm not doing it for free - I feel, that finally I'm able to do something really big for those, who need it
So, if you are one of those, who want me to consult, contact me via this form or Twitter. If you're good consultant and want to join me, contact me via this form or Twitter and we'll make the world better together. I still had no chance to speak with my ex-engagement manager, however I believe, that he will not have a problem with this kind of payment to me. If so (and you have open PO in Microsoft Israel with him), you'll be able to use it. Spear the world with this news! Post in your blogs, twitters, facebook, any other community stuff or just join me :) In WinForms era it was very simple to autoscroll listbox content in order to select last or newly added item. It become a bit complicated in WPF. However, complicated does not mean impossible. As for me, Microsoft should add this feature to base ListBox implementation as another attempt to be attractive environment for LOB application. See, for example this thread from MSDN forums. I'm really understand this guy. He do not want to implement it with a lot of code, he just want it to be included in core WPF control (but he should mark answers) Generally, the simplest way to it is by using attached properties. So, your code will look like this <ListBox Height="200" l:SelectorExtenders.IsAutoscroll="true" IsSynchronizedWithCurrentItem="True" Name="list"/> But what's going on under the hoods? There it bit complicated :) First of all, we should create attached property, named IsAutoscroll public class SelectorExtenders : DependencyObject { public static bool GetIsAutoscroll(DependencyObject obj) { return (bool)obj.GetValue(IsAutoscrollProperty); } public static void SetIsAutoscroll(DependencyObject obj, bool value) { obj.SetValue(IsAutoscrollProperty, value); } public static readonly DependencyProperty IsAutoscrollProperty = DependencyProperty.RegisterAttached("IsAutoscroll", typeof(bool), typeof(SelectorExtenders), new UIPropertyMetadata(default(bool),OnIsAutoscrollChanged)); now handle it when you set it's value by handling new items arrivals, set current and then scroll into it public static void OnIsAutoscrollChanged(DependencyObject s, DependencyPropertyChangedEventArgs e) { var val = (bool)e.NewValue; var lb = s as ListBox; var ic = lb.Items; var data = ic.SourceCollection as INotifyCollectionChanged; var autoscroller = new System.Collections.Specialized.NotifyCollectionChangedEventHandler( (s1, e1) => { object selectedItem = default(object); switch (e1.Action) { case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Move: selectedItem = e1.NewItems[e1.NewItems.Count - 1]; break; case NotifyCollectionChangedAction.Remove: if (ic.Count < e1.OldStartingIndex) { selectedItem = ic[e1.OldStartingIndex - 1]; } else if (ic.Count > 0) selectedItem = ic[0]; break; case NotifyCollectionChangedAction.Reset: if (ic.Count > 0) selectedItem = ic[0]; break; } if (selectedItem != default(object)) { ic.MoveCurrentTo(selectedItem); lb.ScrollIntoView(selectedItem); } }); if (val) data.CollectionChanged += autoscroller; else data.CollectionChanged -= autoscroller; } That's all. Also it can be done by visual tree querying (as thread started proposed). Find scrollviewer inside the ListBox visual tree and then invoke "ScrollToEnd" method of it. Have a nice day and be good people. And, yes, WPF development team should consider this feature implemented internally for any Selector and ScrollViewer control Source code for this article Well, Windows 7 is going to be released by the end of next year. This is great news, because it seemed, that Microsoft finally understand how to get the best of Windows Vista and make it to work not only on monster machines. It even works on new brandy my wife's pinky machine. And if it works there and my wife is happy with it, this OS going to be very impressive. But from the other hand, we, as developers should be ready today to developer Windows 7 ready application (by the way, Vista Battery Saver works for Windows 7 as well as for Windows Vista, in spite of the fact, that power management in Windows 7 was improved dramatically). So let's start! First thing we need is to read big Windows 7 Developer Guide. This document will explain most of new features for developers to build applications right. What is includes? Windows Driver Kit (WDK) 3.0 Basically, Windows 7 works with Vista drivers, however, hibernation, power management, networking, PREfast will work much better. You also will have new WMI access for reliability monitors and ACPI. Management and deployment By default Windows 7 uses PowerShell 2.0 and Windows Installer. For PowerShell it includes enhanced cmdlets to manage Active Directory, IIS, etc. For Windows Installer, you finally can build "chainers" by yourself (the same approach, used for latest deployment of Microsoft products such as Silverlight, Visual Studio 2008 SP1 etc.) Also, you can get advantage by using Windows Filtering Platform (Firewall) and User Account Control (UAC) from inside your application by using new APIs. Performance The most significant change in Windows 7 for end-user point of view is improved performance. Windows 7 kernel is much smaller, that kernel of Windows Vista. Also it uses specific patterns to decrease background activities on low power, based on system triggers. New user-mode and kernel-mode APIs are used by Windows Drivers Foundation much more efficiently. Also system services are much smarter. For example, DCIA starts only when you connect new hardware. After drivers were installed the service shuts down. The same approach used by domain join, GP changes, new IP fetching etc. Windows 7 knows to run and stop services, based on system events, which decreases average work load and enhances whole system performance. Multi-touch gestures and Interia API and used interface in general Yes, you can use this API for your applications. Finally we can have more, then just mouse. And it is not only about multiple mouse devices. We can use single finder panning, raw touch input data, internal multitouch ink recognition, which is also supports math. Also it uses build-in MathML export feature. There are a lot of other enhancements, such as smart bars, windows' stacking, gadget desktop (it does not eat battery as external process anymore), system ribbon menu integration. etc Graphics Direct 11, new Direct2D, DirectWrite (we can turn text anti-aliasing for small fonts, hurrah!), improved WIC, DX/GDI interoperability on system level with automatic fallback for weak hardware (yes, you should not be worry about it anymore). Also new video and audio format support with human readable interfaces. Yes, no more DirectDraw hacks. We can use new high level interfaces such as MFPlay to manage playbacks, Source Reader for decoding, Sink Writer for transcoders and re-coding compressions. Web and communication WCF is inside, as well as distributed routing table for peer-to-peer operations. BranchCache - new technology to reduce WAN traffic and latency. Also Windows 7 is compatible with OpenSearch (I told, that Microsoft does not know to build search engines). Sharepoint integration and environment sensors platform, that can be used either for desktop and web applications. There are much more features, that makes Windows 7 to pretend to be very good operation system. If you want to learn more about all those Windows 7 new features, I highly advice you to download and read this document. It includes most of new features of new OS with explanations and screenshots to make your learn and understand what can your future application do with all those new features. Have a nice day and be good people. BTW, if you have PDC version of Windows 7 and want to unlock it for using of some cool features, introduced during keynotes, it worth to visit here and learn how to :) Download Windows 7 Developer Guide and start programming. I’m working a lot with p/invoke, and know how it’s hard to produce correct signature for unmanaged method. So, today I decided to publish basic cheat sheet for methods, parameters and attributes you should use in order to invoke unmanaged methods from managed code without a lot of problems. We start with data type translations. Here the table to understand it. | Data type from unmanaged signature | Data type in managed signature | | int | int
the same with all other simple types such as double, uint, etc or private objects | | void* | IntPtr | | int* | ref int
the same with all other simple types such as double, uint, etc or private objects | | char** | ref IntPtr
later, you should get ascii string by using System.Runtime.InteropServices.Marshal.PtrToStringAnsi() method | | wcar_t** | ref IntPtr
later, you should get ascii string by using System.Runtime.InteropServices.Marshal.PtrToStringUni() method | | const int* | ref int | | const char* | [System.Runtime.InteropServices.In()] [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] string | | … (variable argument) | [System.Runtime.InteropServices.In()] [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.AsAny)] object | You can use either System.Runtime.InteropServices.In or System.Runtime.InteropServices.Out attribute to specify how arguments should be used. Now we done with simple arguments, let’s see what can be done when argument is actually callback or delegate? | Unmanaged definition | Managed definition | | typedef void (*MyCallback)(int Arg) | [System.Runtime.InteropServices.UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.Cdecl)]delegate void MyCallback(int Arg) Caller cleans stack argument is used to assure, that we can call varargs type function, usually used by API provider. It is very similar to C# overrides for methods. Also you can use StdCall (this is default), ThisCall – stores this first and pushes other parameters on the stack, FastCall – not very supported :( | To call all those methods, we should know managed equivalents of unmanaged types. Here the table. The rule is simple – know how many bytes unmanaged type has and find managed type with the same number of bytes. Other words, you can marshal int into IntPtr too… | Unmanaged type | Managed equivalent | | bool | bool | | char | sbyte (signed), byte (unsigned) | | wchar_t | char | | double | double | | float | single | | int, long (signed) | Int32 | | int, long (unsigned) | UInt32 | | __int64 (signed) | Int64 | | __int64 | UInt64 | | short (signed) | Int16 | | short (unsigned) | UInt16 | | void | void | But not only types are problem in managed/unmanaged transitions. Also structures are aligned differently. For this purpose we can use StructLayout attribute. Even if unmanaged classes are sequential and you used correct managed data types, you can find you with problems in Pack. What “pack” is? Pack is actually slot size in bytes for members of your structure. It can be 0, 1, 2, 4, 8, 16, 32, 64, or 128 and depends on the platform and application setting. Now you can see, that it is not very complicated to create managed signatures when you have header of unmanaged assemblies. So go ahead and ask, if I missed something. That’s all by now. Have a nice day and be good people. Let’s say, that you want to set binding. However, you want to set it by using some trigger. Wait! Why I need it? Let’s say, that I have some very special object, that actually has no hard-coded properties. All it’s properties are created during the runtime, based on some trigger. In this case, I still want to use binding, however, if the property does not exists, I cannot do it. In this special case (that we’ll probably speak more later), we can use this class – TriggerBinding. How to do it? How to extend build-in binding and add such functionality? Simple. All we need is to write custom markup extension. So, we’ll create new one public class TriggerBindingExtension : MarkupExtension And expose all regular binding properties + three custom – TriggerSource, TriggerSourceName and Trigger itself public IValueConverter Converter { get; set; } public object ConverterParameter { get; set; } public string ElementName { get; set; } public RelativeSource RelativeSource { get; set; } public object Source { get; set; } public string TriggerSourceName { get; set; } public UIElement TriggerSource { get; set; } [TypeConverter(typeof(RoutedEventConverter))] public RoutedEvent Trigger { get; set; } public bool ValidatesOnDataErrors { get; set; } public bool ValidatesOnExceptions { get; set; } public string StringFormat { get; set; } [TypeConverter(typeof(EnumConverter))] public BindingMode Mode { get; set; } [TypeConverter(typeof(PropertyPathConverter))] public PropertyPath Path { get; set; } [TypeConverter(typeof(BooleanConverter))] public bool IsAsync { get; set; } public string XPath { get; set; } [TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))] public CultureInfo ConverterCulture { get; set; } Next step is to override ProvideValue method of MarkupExtension and create the actual binding. We still do not want to bind it to any property public override sealed object ProvideValue(IServiceProvider serviceProvider) { var _valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; if (_valueProvider != null) { var _bindingTarget = _valueProvider.TargetObject as FrameworkElement; var _bindingProperty = _valueProvider.TargetProperty as DependencyProperty; var _binding = new Binding(); _binding.Path = Path; _binding.XPath = XPath; _binding.Mode = Mode; _binding.Converter = Converter; _binding.ConverterCulture = ConverterCulture; _binding.ConverterParameter = ConverterParameter; _binding.IsAsync = IsAsync; if (ElementName != null) _binding.ElementName = ElementName; if (RelativeSource != null) _binding.RelativeSource = RelativeSource; if (Source != null) _binding.Source = Source; _binding.ValidatesOnDataErrors = ValidatesOnDataErrors; _binding.ValidatesOnExceptions = ValidatesOnExceptions; _binding.StringFormat = StringFormat; Now, if we have TriggerSource and Trigger, everything fine, we can set subscribe to the trigger event and wire binding in the handler private RoutedEventHandler _bindigHandler; private void _registerHandler(FrameworkElement bindingTarget, DependencyProperty bindingProperty, Binding binding) { if (_bindigHandler == default(RoutedEventHandler)) { _bindigHandler = (RoutedEventHandler)(delegate { var _oldValue = bindingTarget.GetValue(bindingProperty); bindingTarget.SetBinding(bindingProperty, binding); if (_oldValue != bindingProperty.DefaultMetadata.DefaultValue) { bindingTarget.SetValue(bindingProperty, _oldValue); BindingOperations.GetBindingExpression(bindingTarget, bindingProperty).UpdateSource(); } }); TriggerSource = TriggerSource ?? bindingTarget; if (Trigger != null && TriggerSource != null) { TriggerSource.AddHandler(Trigger, _bindigHandler); } else { _bindigHandler.Invoke(this, null); } } } However, we also want to be able to seek Logical tree and find trigger source by it’s name. Let’s say this way <TextBox Name="one" Text="{l:TriggerBinding ElementName=two, Path=Text, Mode=TwoWay, TriggerSourceName=three, Trigger=Button.Click}"/> <TextBox Name="two"/> <Button Name="three" Content="Click to trigger binding"/> What’s the problem? We also have FrameworkElement.FindName method and LogicalTreeHelper.FindLogicalNode. But wait, in our case, when TextBox “one” is initialized, we still have no Button “three”, so we have to wait until the root element of TextBox will be fully loaded and then try to find logical node. For this purpose, we have very useful extended method public static DependencyObject GetRoot(this DependencyObject current) { var _root = current; do { _root = LogicalTreeHelper.GetParent(_root); if (_root != null) current = _root; } while (_root != null); return current; } So, all we have to do is to subscribe to Loaded event of the root and then wire the trigger for set binding if (!string.IsNullOrEmpty(TriggerSourceName)) { var _root = _bindingTarget.GetRoot() as FrameworkElement; _root.Loaded += delegate { TriggerSource = LogicalTreeHelper.FindLogicalNode(_root, TriggerSourceName) as UIElement; _registerHandler(_bindingTarget, _bindingProperty, _binding); }; } else { _registerHandler(_bindingTarget, _bindingProperty, _binding); } We done. Have a good day and be good people. BTW, it’s also possible to postpone binding by using timer. Paul Stovell has very good sample of how to perform it. Source code for this article Finally, I got free minute to convert Silverlight BiDi controls from Silverlight beta 2 to RC0 (you can download SL rc0 tools for VS2008 here) and as usual some breaking changes (the full list is here) - Calendar and DatePicker moved from System.Windows.Controls.Extended into System.Windows.Controls – Extended namespace is now deprecated.
- CalendarButton is not inside System.Windows.Controls.Primitives
- TypeConverter.CanConvertFrom(Type sourceType) was changed and now it has new first parameter ITypeDescriptorContext context
- TypeConverter.CanConvertFrom(object value) was changed and now it has new first parameter ITypeDescriptorContext context and second parameter System.Globalization.CultureInfo culture
- TypeConverter.ConvertFromString is not virtual anymore
- TextDecorationCollectionConverter was removed
- generic.xaml should be placed into themes directory (as in WPF)
- VisualTransition.Duration is not VisualTransition.GeneratedDuration
- ContentPresenter has no HorizontalContentAlignment and VerticalContentAlignment. It has HorizontalAlignment and VerticalAlignment now. Also it has no Background, Padding,TextAlignment,TextDecorations and TextWrapping properties
Those, basically, all changes done in Silverlight RTL support library. So, you can download and use the latest version within Silverlight RC0 version Have a nice day and be good people. MSDN download center just published video catalogs, where you can find sample chapters of Total Training’s video series for Microsoft Expression studio. Worth to download and see. Here the breakdown by products Also, if you are “in” Office development, it makes sense to take a look in to Data Connectivity Components for Office 2007 System. It can be used by non-Microsoft Office applications to read data from Office system files. Happy downloading! Three days ago, I announced the new release of WPF Performance Profiling Tool. A couple hours after this was announced, I got a number of comments from you, readers. It was about issues with running this tool. I checked the issue and forwarded it to development team from Microsoft. The problem was in bad parsing of comma and point characters in this tool, when using it on non-US locale. Dev team took care about it and hopefully they will provide a fix soon. Thank you for reading my blog, reporting and your awareness of such issues. This is very important to me and I’m really appreciate your efforts to help us to develop WPF community. I’ll update you as soon as the patch will be available. Thank you and Shana Tova! 
New version of outstanding Exchange synchronization tool for Nokia phones is out. There are no big changes, except heart beat implementation of ActiveSync and fixes to contacts mapping bug is there. The main feature of this version is, that it can now run on any S60 phone (including 3.0, 3.1 and 3.2 devices), not only on E and N series. Full list of changes can be found here. The bad news is, that Nokia MFE 2.7 still does not support inbox folders synchronization. This is the main feature missing in this great software. However, according the update rate, it should arrive soon. Download Nokia Mail for Exchange 2.7 >> Chicks love CodePlex as well as Microsoft loves it too and today they release extremely useful tool, that was internal for more, then three years. It named: “Visual Studio Snippet Designer”. As you can, probably, understand. This tools is used to create and manage VS time savers - snippet files (introduced in VS2005) This is great tool, that will help you a lot to save your time during regular everyday development. Any other word is unnecessary. Download, install and use it! I understand, when I have internet access problems in hotels, airports and other public places. I even understand, when public WiFi along eThekwini shores in KwaZulu works slow. However, I cannot understand, why 012 golden lines (together with Internet Zahav) cannot solve simple loss packet problems large web resources? Tracing route to googlemail.l.google.com [66.249.91.83] over a maximum of 30 hops: 1 2 ms 5 ms 2 ms 192.168.1.1 2 11 ms 19 ms 9 ms 10.198.80.1 3 10 ms 22 ms 13 ms 213.57.43.199 4 * * * Request timed out. 5 138 ms 144 ms * 77.67.61.145 6 149 ms 138 ms 142 ms xe-0-0-0.ams10.ip.tiscali.net [89.149.186.233] Tracing route to googlemail.l.google.com [66.249.91.83] over a maximum of 30 hops: 1 31 ms 3 ms 5 ms 192.168.1.1 2 10 ms 9 ms 8 ms 10.198.80.1 3 * * * Request timed out. 4 * 11 ms * 212.117.151.113.static.012.net.il [212.117.151.113] 5 * * * Request timed out. 6 124 ms 127 ms 127 ms xe-1-0-0.ams10.ip.tiscali.net [89.149.186.241] Tracing route to googlemail.l.google.com [66.249.91.83] over a maximum of 30 hops: 1 2 ms 2 ms 2 ms 192.168.1.1 2 10 ms 19 ms 18 ms 10.198.80.1 3 * 23 ms 24 ms 213.57.43.199 4 * * 15 ms 213.57.43.22 5 * 139 ms * 77.67.61.145 6 133 ms 125 ms 139 ms xe-1-0-0.ams10.ip.tiscali.net [89.149.186.241] Tracing route to googlemail.l.google.com [209.85.137.18] over a maximum of 30 hops: 1 2 ms 2 ms 2 ms 192.168.1.1 2 33 ms 10 ms 12 ms 10.198.80.1 3 * 24 ms 11 ms 213.57.43.199 4 11 ms * * 213.57.43.22 5 * * * Request timed out. 6 126 ms 133 ms 133 ms xe-0-0-0.ams10.ip.tiscali.net [89.149.186.233] Yes, I have a router, I even have more, then one computer connected. Also I wont connect via your VPN, I want direct PPPoE access and I paying for it extra money to you. So why it is too complicated to just fix broken proxy server? Why you want to me every time to connect directly to the modem and see the same problem? Why it is not enough for you, when cable operator connects me to another internet provider and the problem disappears? Why it is a problem to fix broken things? WHY? Maybe I should sue you for 5 years of such service, I’m paying for? Maybe only this will force you to do something with it! "The reports of my death are greatly exaggerated" Since my post about leaving consulting field, I got huge amount of email with questions about all community projects, I'm leading, blogging in general and specific to the future of this blog. To make things clear, I leaved consulting, and now, I have less time and reasons to blog, however, I'm keep blogging and maintaining almost all of my community projects (see the left side of the main page to list of most of those projects). Also, I try to answer all questions, I got via emails, however it's too much to handle, thus be prepared for delays. To be sure, I'm alive, you can follow me at twitter (it demands less time to write) :) Anyway, that you for reading my blog and supporting me. Now it's good time to write something useful for you :) If you ever wrote GIS programs, you, probably, know, that every time we forget how to convert latlon (Latitude-Longitude or, simpler World Geographical System) coordinates into Universal Transverse Mercator coordinates and vise verse, how to calculate geographical distance from point to point, line or segment to point and line to line, how to calculate azimuth between two geo points, or how to calculate destination point, based on start coordinate, distance and bearing. In order to make our life easier, I decided to post number of methods in C#, I always use to perform such GIS calculations. I believe, that those classes will help you to perform your own geographical calculations. Also, do not forget two handy functions to convert degrees to radians and vv public static double DegToRad(double deg) { return (deg / 180.0 * Math.PI); } public static double RadToDeg(double rad) { return (rad / Math.PI * 180.0); } Also, there are some constants should be used, if you're calculating geo information in the Earth const double sm_a = 6378137.0; const double sm_b = 6356752.314; const double sm_EccSquared = 6.69437999013e-03; const double UTMScaleFactor = 0.9996; Conversion WGS2UTM (LatLon2UTM) Fist of all, we should calculate UTM zone. This one is simple int zone = (int)(Math.Floor((latlon.Longitude + 180.0) / 6) + 1); Now, when we have zone, we should calculate UTM central meridian, footprint of latitude and arc length of the meridian public static double UTMCentralMeridian(int zone) { return DegToRad(-183.0 + (zone * 6.0)); }
public static double FootpointLatitude(double y) { /* Precalculate n (Eq. 10.18) */ var n = (sm_a - sm_b) / (sm_a + sm_b); /* Precalculate alpha_ (Eq. 10.22) */ /* (Same as alpha in Eq. 10.17) */ var alpha_ = ((sm_a + sm_b) / 2.0) * (1 + (Math.Pow(n, 2.0) / 4) + (Math.Pow(n, 4.0) / 64)); /* Precalculate y_ (Eq. 10.23) */ var y_ = y / alpha_; /* Precalculate beta_ (Eq. 10.22) */ var beta_ = (3.0 * n / 2.0) + (-27.0 * Math.Pow(n, 3.0) / 32.0) + (269.0 * Math.Pow(n, 5.0) / 512.0); /* Precalculate gamma_ (Eq. 10.22) */ var gamma_ = (21.0 * Math.Pow(n, 2.0) / 16.0) + (-55.0 * Math.Pow(n, 4.0) / 32.0); /* Precalculate delta_ (Eq. 10.22) */ var delta_ = (151.0 * Math.Pow(n, 3.0) / 96.0) + (-417.0 * Math.Pow(n, 5.0) / 128.0); /* Precalculate epsilon_ (Eq. 10.22) */ var epsilon_ = (1097.0 * Math.Pow(n, 4.0) / 512.0); /* Now calculate the sum of the series (Eq. 10.21) */ return y_ + (beta_ * Math.Sin(2.0 * y_)) + (gamma_ * Math.Sin(4.0 * y_)) + (delta_ * Math.Sin(6.0 * y_)) + (epsilon_ * Math.Sin(8.0 * y_)); }
public static double ArcLengthOfMeridian(double phi) { /* Precalculate n */ var n = (sm_a - sm_b) / (sm_a + sm_b); /* Precalculate alpha */ var alpha = ((sm_a + sm_b) / 2.0) * (1.0 + (Math.Pow(n, 2.0) / 4.0) + (Math.Pow(n, 4.0) / 64.0)); /* Precalculate beta */ var beta = (-3.0 * n / 2.0) + (9.0 * Math.Pow(n, 3.0) / 16.0) + (-3.0 * Math.Pow(n, 5.0) / 32.0); /* Precalculate gamma */ var gamma = (15.0 * Math.Pow(n, 2.0) / 16.0) + (-15.0 * Math.Pow(n, 4.0) / 32.0); /* Precalculate delta */ var delta = (-35.0 * Math.Pow(n, 3.0) / 48.0) + (105.0 * Math.Pow(n, 5.0) / 256.0); /* Precalculate epsilon */ var epsilon = (315.0 * Math.Pow(n, 4.0) / 512.0); /* Now calculate the sum of the series and return */ return alpha * (phi + (beta * Math.Sin(2.0 * phi)) + (gamma * Math.Sin(4.0 * phi)) + (delta * Math.Sin(6.0 * phi)) + (epsilon * Math.Sin(8.0 * phi))); } Now, we have everything to calculate UTM public static GeoPoint MapLatLonToXY(double phi, double lambda, double lambda0) { /* Precalculate ep2 */ var ep2 = (Math.Pow(sm_a, 2.0) - Math.Pow(sm_b, 2.0)) / Math.Pow(sm_b, 2.0); /* Precalculate nu2 */ var nu2 = ep2 * Math.Pow(Math.Cos(phi), 2.0); /* Precalculate N */ var N = Math.Pow(sm_a, 2.0) / (sm_b * Math.Sqrt(1 + nu2)); /* Precalculate t */ var t = Math.Tan(phi); var t2 = t * t; var tmp = (t2 * t2 * t2) - Math.Pow(t, 6.0); /* Precalculate l */ var l = lambda - lambda0; /* Precalculate coefficients for l**n in the equations below so a normal human being can read the expressions for easting and northing -- l**1 and l**2 have coefficients of 1.0 */ var l3coef = 1.0 - t2 + nu2; var l4coef = 5.0 - t2 + 9 * nu2 + 4.0 * (nu2 * nu2); var l5coef = 5.0 - 18.0 * t2 + (t2 * t2) + 14.0 * nu2 - 58.0 * t2 * nu2; var l6coef = 61.0 - 58.0 * t2 + (t2 * t2) + 270.0 * nu2 - 330.0 * t2 * nu2; var l7coef = 61.0 - 479.0 * t2 + 179.0 * (t2 * t2) - (t2 * t2 * t2); var l8coef = 1385.0 - 3111.0 * t2 + 543.0 * (t2 * t2) - (t2 * t2 * t2); var xy = new GeoPoint(); /* Calculate easting (x) */ xy.X = N * Math.Cos(phi) * l + (N / 6.0 * Math.Pow(Math.Cos(phi), 3.0) * l3coef * Math.Pow(l, 3.0)) + (N / 120.0 * Math.Pow(Math.Cos(phi), 5.0) * l5coef * Math.Pow(l, 5.0)) + (N / 5040.0 * Math.Pow(Math.Cos(phi), 7.0) * l7coef * Math.Pow(l, 7.0)); /* Calculate northing (y) */ xy.Y = ArcLengthOfMeridian(phi) + (t / 2.0 * N * Math.Pow(Math.Cos(phi), 2.0) * Math.Pow(l, 2.0)) + (t / 24.0 * N * Math.Pow(Math.Cos(phi), 4.0) * l4coef * Math.Pow(l, 4.0)) + (t / 720.0 * N * Math.Pow(Math.Cos(phi), 6.0) * l6coef * Math.Pow(l, 6.0)) + (t / 40320.0 * N * Math.Pow(Math.Cos(phi), 8.0) * l8coef * Math.Pow(l, 8.0)); return xy; }
public static GeoCoord MapXYToLatLon(double x, double y, double lambda0) { /* Get the value of phif, the footpoint latitude. */ double phif = FootpointLatitude(y); /* Precalculate ep2 */ double ep2 = (Math.Pow(sm_a, 2.0) - Math.Pow(sm_b, 2.0)) / Math.Pow(sm_b, 2.0); /* Precalculate cos (phif) */ var cf = Math.Cos(phif); /* Precalculate nuf2 */ var nuf2 = ep2 * Math.Pow(cf, 2.0); /* Precalculate Nf and initialize Nfpow */ var Nf = Math.Pow(sm_a, 2.0) / (sm_b * Math.Sqrt(1 + nuf2)); var Nfpow = Nf; /* Precalculate tf */ var tf = Math.Tan(phif); var tf2 = tf * tf; var tf4 = tf2 * tf2; /* Precalculate fractional coefficients for x**n in the equations below to simplify the expressions for latitude and longitude. */ var x1frac = 1.0 / (Nfpow * cf); Nfpow *= Nf; /* now equals Nf**2) */ var x2frac = tf / (2.0 * Nfpow); Nfpow *= Nf; /* now equals Nf**3) */ var x3frac = 1.0 / (6.0 * Nfpow * cf); Nfpow *= Nf; /* now equals Nf**4) */ var x4frac = tf / (24.0 * Nfpow); Nfpow *= Nf; /* now equals Nf**5) */ var x5frac = 1.0 / (120.0 * Nfpow * cf); Nfpow *= Nf; /* now equals Nf**6) */ var x6frac = tf / (720.0 * Nfpow); Nfpow *= Nf; /* now equals Nf**7) */ var x7frac = 1.0 / (5040.0 * Nfpow * cf); Nfpow *= Nf; /* now equals Nf**8) */ var x8frac = tf / (40320.0 * Nfpow); /* Precalculate polynomial coefficients for x**n. -- x**1 does not have a polynomial coefficient. */ var x2poly = -1.0 - nuf2; var x3poly = -1.0 - 2 * tf2 - nuf2; var x4poly = 5.0 + 3.0 * tf2 + 6.0 * nuf2 - 6.0 * tf2 * nuf2 - 3.0 * (nuf2 * nuf2) - 9.0 * tf2 * (nuf2 * nuf2); var x5poly = 5.0 + 28.0 * tf2 + 24.0 * tf4 + 6.0 * nuf2 + 8.0 * tf2 * nuf2; var x6poly = -61.0 - 90.0 * tf2 - 45.0 * tf4 - 107.0 * nuf2 + 162.0 * tf2 * nuf2; var x7poly = -61.0 - 662.0 * tf2 - 1320.0 * tf4 - 720.0 * (tf4 * tf2); var x8poly = 1385.0 + 3633.0 * tf2 + 4095.0 * tf4 + 1575 * (tf4 * tf2); var philambda = new GeoCoord(); /* Calculate latitude */ philambda.Latitude = phif + x2frac * x2poly * (x * x) + x4frac * x4poly * Math.Pow(x, 4.0) + x6frac * x6poly * Math.Pow(x, 6.0) + x8frac * x8poly * Math.Pow(x, 8.0); /* Calculate longitude */ philambda.Longitude = lambda0 + x1frac * x + x3frac * x3poly * Math.Pow(x, 3.0) + x5frac * x5poly * Math.Pow(x, 5.0) + x7frac * x7poly * Math.Pow(x, 7.0); return philambda; } We done, the only thing, should be adjusted is easting and northing for UTM system xy.X = xy.X * UTMScaleFactor + 500000.0; xy.Y = xy.Y * UTMScaleFactor; if (xy.Y < 0.0) xy.Y += 10000000.0; Conversion UTM2WGS (UTM2LatLon) After al had all thin math in previous chapter, now we should calculate opposite conversion First adjust x -= 500000.0; x /= UTMScaleFactor; /* If in southern hemisphere, adjust y accordingly. */ if (southhemi) y -= 10000000.0; y /= UTMScaleFactor; var cmeridian = UTMCentralMeridian(zone); Now calculate public static GeoCoord MapXYToLatLon(double x, double y, double lambda0) { /* Get the value of phif, the footpoint latitude. */ double phif = FootpointLatitude(y); /* Precalculate ep2 */ double ep2 = (Math.Pow(sm_a, 2.0) - Math.Pow(sm_b, 2.0)) / Math.Pow(sm_b, 2.0); /* Precalculate cos (phif) */ var cf = Math.Cos(phif); /* Precalculate nuf2 */ var nuf2 = ep2 * Math.Pow(cf, 2.0); /* Precalculate Nf and initialize Nfpow */ var Nf = Math.Pow(sm_a, 2.0) / (sm_b * Math.Sqrt(1 + nuf2)); var Nfpow = Nf; /* Precalculate tf */ var tf = Math.Tan(phif); var tf2 = tf * tf; var tf4 = tf2 * tf2; /* Precalculate fractional coefficients for x**n in the equations below to simplify the expressions for latitude and longitude. */ var x1frac = 1.0 / (Nfpow * cf); Nfpow *= Nf; /* now equals Nf**2) */ var x2frac = tf / (2.0 * Nfpow); Nfpow *= Nf; /* now equals Nf**3) */ var x3frac = 1.0 / (6.0 * Nfpow * cf); Nfpow *= Nf; /* now equals Nf**4) */ var x4frac = tf / (24.0 * Nfpow); Nfpow *= Nf; /* now equals Nf**5) */ var x5frac = 1.0 / (120.0 * Nfpow * cf); Nfpow *= Nf; /* now equals Nf**6) */ var x6frac = tf / (720.0 * Nfpow); Nfpow *= Nf; /* now equals Nf**7) */ var x7frac = 1.0 / (5040.0 * Nfpow * cf); Nfpow *= Nf; /* now equals Nf**8) */ var x8frac = tf / (40320.0 * Nfpow); /* Precalculate polynomial coefficients for x**n. -- x**1 does not have a polynomial coefficient. */ var x2poly = -1.0 - nuf2; var x3poly = -1.0 - 2 * tf2 - nuf2; var x4poly = 5.0 + 3.0 * tf2 + 6.0 * nuf2 - 6.0 * tf2 * nuf2 - 3.0 * (nuf2 * nuf2) - 9.0 * tf2 * (nuf2 * nuf2); var x5poly = 5.0 + 28.0 * tf2 + 24.0 * tf4 + 6.0 * nuf2 + 8.0 * tf2 * nuf2; var x6poly = -61.0 - 90.0 * tf2 - 45.0 * tf4 - 107.0 * nuf2 + 162.0 * tf2 * nuf2; var x7poly = -61.0 - 662.0 * tf2 - 1320.0 * tf4 - 720.0 * (tf4 * tf2); var x8poly = 1385.0 + 3633.0 * tf2 + 4095.0 * tf4 + 1575 * (tf4 * tf2); var philambda = new GeoCoord(); /* Calculate latitude */ philambda.Latitude = phif + x2frac * x2poly * (x * x) + x4frac * x4poly * Math.Pow(x, 4.0) + x6frac * x6poly * Math.Pow(x, 6.0) + x8frac * x8poly * Math.Pow(x, 8.0); /* Calculate longitude */ philambda.Longitude = lambda0 + x1frac * x + x3frac * x3poly * Math.Pow(x, 3.0) + x5frac * x5poly * Math.Pow(x, 5.0) + x7frac * x7poly * Math.Pow(x, 7.0); return philambda; } At the end do not forget to return Latitude and Longitude in degrees, rather, then in radians Azimuth calculation We done with coordinates, now azimuth public static double GetAzimuth(WGSCoord c1, WGSCoord c2) { var lat1 = DegToRad(c1.Latitude); var lon1 = DegToRad(c1.Longitude); var lat2 = DegToRad(c2.Latitude); var lon2 = DegToRad(c2.Longitude); return RadToDeg(Math.Asin(Math.Sin(lon1 - lon2) * Math.Cos(lat2) / Math.Sin(Math.Acos(Math.Sin(lat2) * Math.Sin(lat1) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Cos(lon2 - lon1))))); } Distance calculations public static double GetDistance(WGSCoord c1, WGSCoord c2) { var dLat = DegToRad(c2.Latitude - c1.Latitude); var dLon = DegToRad(c2.Longitude - c2.Longitude); var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + Math.Cos(DegToRad(c1.Latitude)) * Math.Cos(DegToRad(c2.Latitude)) * Math.Sin(dLon / 2) * Math.Sin(dLon / 2); var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a)); return sm_a * c; } Final destination coordinates, based on start coordinate, bearing and distance public static WGSCoord GetDestination(WGSCoord start, double distance, double azimuth) { var lat = RadToDeg(Math.Asin(Math.Sin(DegToRad(start.Latitude)) * Math.Cos(DegToRad(distance / sm_a)) + Math.Cos(DegToRad(start.Latitude)) * Math.Sin(DegToRad(distance / sm_a)) * Math.Cos(DegToRad(azimuth)))); var lon = start.Longitude + DegToRad(Math.Atan2(Math.Sin(DegToRad(azimuth)) * Math.Sin(DegToRad(distance / sm_a)) * Math.Cos(DegToRad(start.Latitude)), Math.Cos(DegToRad(distance / sm_a)) - Math.Sin(DegToRad(start.Latitude)) * Math.Sin(DegToRad(lat)))); return new WGSCoord(lon,lat); } Midpoint calculation public static WGSCoord GetMidpoint(WGSCoord start, WGSCoord end) { var dLat = DegToRad(end.Latitude - start.Latitude); var dLon = DegToRad(end.Longitude - start.Longitude); var lat2 = DegToRad(end.Latitude); var lat1 = DegToRad(start.Latitude); var lon1 = DegToRad(start.Longitude); var Bx = Math.Cos(lat2) * Math.Cos(dLon); var By = Math.Cos(lat2) * Math.Sin(dLon); var lat3 = Math.Atan2(Math.Sin(lat1) + Math.Sin(lat2), Math.Sqrt((Math.Cos(lat1) + Bx) * (Math.Cos(lat1) + Bx) + By * By)); var lon3 = lon1 + Math.Atan2(By, Math.Cos(lat1) + Bx); return new WGSCoord(RadToDeg(lon3), RadToDeg(lat3)); } Rhumb lines calculation First of all, what is rhumb lines? Rhumb lines or loxodrome is a path of constant bearing, which crosses all meridians at the same angle. This calculation is very useful, if you want to follow constant compass bearing, instead of continually adjustment of it. public static double GetRhumbDistance(WGSCoord start, WGSCoord end) { var dLat = DegToRad(end.Latitude - start.Latitude); var dLon = DegToRad(end.Longitude - start.Longitude); var lat1 = DegToRad(start.Latitude); var lon1 = DegToRad(start.Longitude); var lat2 = DegToRad(end.Latitude); var lon2 = DegToRad(end.Longitude); var dPhi = Math.Log(Math.Tan(lat2 / 2 + Math.PI / 4) / Math.Tan(lat1 / 2 + Math.PI / 4)); var q = (Math.Abs(dLat) > 1e-10) ? dLat / dPhi : Math.Cos(lat1); if (Math.Abs(dLon) > Math.PI) { dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon); } return Math.Sqrt(dLat * dLat + q * q * dLon * dLon) * sm_a; } public static double GetRhumbBearing(WGSCoord start, WGSCoord end) { var dLat = DegToRad(end.Latitude - start.Latitude); var dLon = DegToRad(end.Longitude - start.Longitude); var lat1 = DegToRad(start.Latitude); var lon1 = DegToRad(start.Longitude); var lat2 = DegToRad(end.Latitude); var lon2 = DegToRad(end.Longitude); var dPhi = Math.Log(Math.Tan(lat2 / 2 + Math.PI / 4) / Math.Tan(lat1 / 2 + Math.PI / 4)); if (Math.Abs(dLon) > Math.PI) { dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon); } return Math.Atan2(dLon, dPhi); } public static WGSCoord GetRhumbDestination(WGSCoord start, WGSCoord end) { var lat1 = DegToRad(start.Latitude); var lon1 = DegToRad(start.Longitude); var lat2 = DegToRad(end.Latitude); var lon2 = DegToRad(end.Longitude); var d = GetRhumbDistance(start, end); var b = GetRhumbBearing(start, end); lat2 = lat1 + d * Math.Cos(b); var dPhi = Math.Log(Math.Tan(lat2 / 2 + Math.PI / 4) / Math.Tan(lat1 / 2 + Math.PI / 4)); var q = (Math.Abs(lat2 - lat1) > 1e-10) ? (lat2 - lat1) / dPhi : Math.Cos(lat1); var dLon = d * Math.Sin(b) / q; if (Math.Abs(lat2) > Math.PI / 2) lat2 = lat2 > 0 ? Math.PI - lat2 : -Math.PI - lat2; lon2 = (lon1 + dLon + Math.PI) % (2 * Math.PI) - Math.PI; return new WGSCoord(RadToDeg(lon2), RadToDeg(lat2)); } Conversion between decimal degrees to degrees-minutes-seconds public static Pair<double,double> ToD_Mm(this double decCoord) { var degSeg = (int)decCoord; return new Pair<double, double>(degSeg, decCoord % degSeg * 60); } Now it's good time for small math (if there were not enough until now) :) All following methods are not geo spatial oriented. All those are only geometry. The only helper method, you need here is Dot vector operation private static double dot(Vector v1, Vector v2) { return (v1.X * v2.X + v1.Y * v2.Y); } Get the distance between two points private static double distance(Point p1, Point p2) { var a = Math.Pow(p1.X, 2) - Math.Pow(p2.X, 2); var b = Math.Pow(p1.Y, 2) - Math.Pow(p2.Y, 2); return Math.Sqrt(Math.Abs(a + b)); } Get the shortest distance between point and line segment private static double distance(Point p1, Point p2, Point p) { var v = p1 - p2; var w = p - p2; var c1 = dot(w, v); if (c1 <= 0) return distance(p, p1); var c2 = dot(v, v); if (c2 <= c1) return distance(p, p2); var b = c1 / c2; Point Pb = p1 + b * v; return distance(p, Pb); } Isn't it enough math for now? It is. Have a nice day and be good people. Next time, we'll speak about different path finding algorithms, edges, nodes and other fun math optimizations. If you're "in" WPF and/or Silverlight development and want to learn more about high performance programming in WPF or your ability to develop once for WPF and Silverlight, you're invited to vote for my session in Dev Academy 3 and attend it: - High performance programming with WPF in .NET framework 3.5 SP1
.NET 3.5 SP1 (Arrowhead) brings you full power of WPF by taking into account huge performance enhancements for Line-Of-Business by using data virtualization and high graphical applications by access to DirectX surfaces. This session will round up all new features to Arrowhead - Unified development experience with Windows Presentation Foundation and Silverlight 2.0
Two years ago, Microsoft introduced Windows Presentation Foundation (WPF) to provide a unified platform for Rich Windows Client Applications development. A year later Silverlight was introduced as similar platform for Rich Internet Applications. Is it possible to use XAML based approach to share and reuse code for both frameworks? In this session we will learn how to develop reusable code base for productive, usable and well branded Client and Internet applications to wider distribution, demanded today. Also, you can advice another session, you might be interested in via comments in this post Thank you and see you there... Dear readers, I leaved mPrest, the company, I worked for last 5 years and joined the project, that aspires to make the World better. Currently, I cannot disclosure too much, but briefly, I will not consult and manage client's projects anymore. This does not mean, that I'm quitting Microsoft and it's technologies, I'm just beginning to develop product(s). Also, I'll keep blogging. It will be probably less "problem solutions" and more "how the stuff works", but actually, I'll blog less. In addition, I promised to Microsoft, that I'll contribute some of my time for community activities such as lectures, QnA sessions, presentations and open source activities. But this will be only small part of my time. Mainly, I'll work to accomplish my project. All my contact information remains the same, so, if you have urgent questions, you can ask. Also, if you want to be in touch more, you can follow me in Twitter. © Image copyright by National Geographic, photographer - O. Louis Mazzatenta Thank you, and keep reading... Tamir
|
|
|
|