<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>tydlevidle.cz &#187; Uncategorized</title>
	<atom:link href="http://www.tydlevidle.cz/category/uncategorized/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.tydlevidle.cz</link>
	<description>cartography, geoinformatics.. and all things GIS</description>
	<lastBuildDate>Sun, 19 Apr 2009 17:59:12 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Thematic cartography in ArcGIS</title>
		<link>http://www.tydlevidle.cz/2009/04/thematic-cartography-in-arcgis/</link>
		<comments>http://www.tydlevidle.cz/2009/04/thematic-cartography-in-arcgis/#comments</comments>
		<pubDate>Sun, 19 Apr 2009 17:59:12 +0000</pubDate>
		<dc:creator>petr k.</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[cartography]]></category>
		<category><![CDATA[ESRI]]></category>
		<category><![CDATA[gis]]></category>
		<category><![CDATA[symbology]]></category>
		<category><![CDATA[thematic cartography]]></category>

		<guid isPermaLink="false">http://www.tydlevidle.cz/?p=102</guid>
		<description><![CDATA[<p>In the past few weeks I&#8217;ve been reiterating my knowledge of thematic<br />
cartography principles and rules I was taught at geographical cartography<br />
course few years ago. I do not know what exactly triggered my interest again,<br />
but anyway, I quickly rediscovered the beauty of thematic cartography and the<br />
many ways it can convey and communicate spatial information. And as a media of<br />
its [...]</p>
]]></description>
			<content:encoded><![CDATA[
<p>In the past few weeks I've been reiterating my knowledge of thematic
cartography principles and rules I was taught at geographical cartography
course few years ago. I do not know what exactly triggered my interest again,
but anyway, I quickly rediscovered the beauty of thematic cartography and the
many ways it can convey and communicate spatial information. And as a media of
its own, maps can also <a
href="http://www.amazon.com/How-Lie-Maps-Mark-Monmonier/dp/0226534219">lie</a>,
of course.</p>

<p>One of the most widely used tools in thematic cartography are <a
href="http://en.wikipedia.org/wiki/Choropleth_map">choropleths</a> and
„carto-charts“. In comparison to other commercial tools,
ArcMap's capa­bilities are quite advanced but still not ideal, not to mention
open source projects where complex feature symbolization is very often one of
the last items on their to-do lists. ArcMap comes with a decent set of
<strong>renderers</strong> – their job is to draw the features the way you
like, symbolizing your data in some way.</p>

<h3>Map symbology</h3>

<p>Symbolization is one the keys to your map's readibility. Cartographic
symbols can carry more information than one would think at first. Even in the
simplest form, any symbol, say a plain old circle, is capable of expressing many
variables by <strong>variating its properties</strong>. Its size can tell you
about a city population – either broken down into categories or characterized
by a functional relationship between the symbol size and the population. The
fill color can then express its administrative function. If you add the circle
outline width into the mix, you can have additional piece of information to say,
e.g., whether the city has an university (thick outline) or not (thin outline).
There are loads of other symbol properties you can use to express something, in
our example it could also be the outline style (solid, dashed), the fill
pattern, the shape (not only a circle, but also a square, a diamond etc.) or
symbol orientation (which does not work exactly well for circles of course).</p>

<p>This is not easy to do at all in ArcMap or any other tools at the market.
This could be seen as an advantage as you are not tempted to employ a complex
symbology which could <strong>result in information overload</strong> and your
map ending up as an unreadable mess. But I think you <em>should</em> have these
tools at hand if you feel the need.</p>

<p>Another problem often encountered is generating a proper and good looking
<strong>legend</strong>, which is just equally important as your symbol design.
This is even a bigger issue in the present tools. Having a nice <strong>graph
for proportional symbols</strong> in the map legend is nearly impossible, but is
one of the most common things you will find in a professional thematic map.
I believe this may very well be <em>the</em> distinction between good and bad
map, if you were to choose one.</p>

<h3>Specialized renderer project</h3>

<p>These are some of the key reasons why I started a little personal project of
mine aiming to implement some of the behavior described above in ArcGIS. While
I believe there would be many users who would benefit from such toolset, it is
mostly a self-amusing job. First prototype is capable of variating most
properties of marker symbols, drive them by field value (either into break the
values into categories or use a functional relationship if applicable), but the
design strives to be transparent accross all symbol types. Main features will
include:</p>

<ul>
	<li>Variating symbol properties like size, color, fill etc.</li>

	<li>Properties can be dependent on database field values either by breaking them
	into intervals or specifying a function.</li>

	<li>Properties can be set „recursively“, e.g. if you want to vary a
	symbol's fill, you can also vary the fill's color or pattern in the same way
	(for example again by using field values).</li>
</ul>

<p>Once I implement a decent UI for the renderer, which is arguably the hardest
part to get right as it needs to stay simple and clean, I'll post more on this,
including some real-world samples and screenshots. Another step will be the
legend, which is still in the early design stage as it gets quite complex if you
want it to be reasonably flexible.</p>

<p>Stay tuned, more information is to come soon. If you have any suggestions,
feel free to express them right away!</p>

<!-- by Texy2! -->]]></content:encoded>
			<wfw:commentRss>http://www.tydlevidle.cz/2009/04/thematic-cartography-in-arcgis/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Exposing COM events from .NET: Implementing MapSurround in ArcMap</title>
		<link>http://www.tydlevidle.cz/2009/03/exposing-com-events-from-net-implementing-mapsurround-in-arcmap/</link>
		<comments>http://www.tydlevidle.cz/2009/03/exposing-com-events-from-net-implementing-mapsurround-in-arcmap/#comments</comments>
		<pubDate>Fri, 13 Mar 2009 15:49:33 +0000</pubDate>
		<dc:creator>petr k.</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[ArcMap]]></category>
		<category><![CDATA[COM]]></category>
		<category><![CDATA[ESRI]]></category>
		<category><![CDATA[gis]]></category>
		<category><![CDATA[interop]]></category>
		<category><![CDATA[programming]]></category>

		<guid isPermaLink="false">http://www.tydlevidle.cz/?p=12</guid>
		<description><![CDATA[<p>Recently I needed to implement a custom map surround object in ArcMap from<br />
within the .NET framework environment. The legend, scale bar, north arrow are<br />
all examples of a map surround. What is specific to these objects is that they<br />
are associated with a particular map (data frame) and are typically created via<br />
IMap.CreateMap­Surround<br />
method. All map surround objects extend the<br />
MapSurround [...]</p>
]]></description>
			<content:encoded><![CDATA[
<p>Recently I needed to implement a custom map surround object in ArcMap from
within the .NET framework environment. The legend, scale bar, north arrow are
all examples of a map surround. What is specific to these objects is that they
are associated with a particular map (data frame) and are typically created via
<a
href="http://resources.esri.com/help/9.3/ArcGISDesktop/ArcObjects/esriCarto/IMap_CreateMapSurround.htm">IMap.CreateMap­Surround</a>
method.<span id="more-12"></span> All map surround objects extend the
MapSurround COM abstract class, which is comprised of these interfaces:</p>

<ul>
	<li><a
	href="http://resources.esri.com/help/9.3/ArcGISDesktop/ArcObjects/esriCarto/IBoundsProperties.htm">IBoundsProperties</a></li>

	<li>IClone</li>

	<li>IConnectionPo­intContainer</li>

	<li><a
	href="http://resources.esri.com/help/9.3/ArcGISDesktop/ArcObjects/esriCarto/IMapSurround.htm">IMapSurround</a></li>

	<li><a
	href="http://resources.esri.com/help/9.3/ArcGISDesktop/ArcObjects/esriCarto/IMapSurroundEvents.htm">IMapSurroundE­vents</a>
	(source interface)</li>

	<li>IPersist</li>

	<li>IPersistStream</li>
</ul>

<p>Clearly, some of these interfaces (cloning and persistence) are not as
interesting as the others. In this article I'll focus mainly on the event
supporting interfaces, as these are (arguably) the hardest to implement.
Unfortunately, the ArcObjects documentation and library reference provide
virtually no guidelines on approaching this particular problem. I can only
speculate this stems from the fact that this kind of task was not possible in
VB6 at all and since the .NET documentation seems to be mostly converted from
the VB6 docs with some additional .NET-specific topics, it was simply
left out.</p>

<p>Chances are this article will be also useful outside the ESRI stack world as
it deals with some of COM interop issues in a generic fashion.</p>

<h3>COM source interfaces as .NET events</h3>

<p>Let me first elaborate on how COM source interfaces are converted into the
.NET event/delegate model. This task is done through the <a
href="http://msdn.microsoft.com/en-us/library/tt0cf3sx(VS.80).aspx">Type Library
Importer utility</a> (tlbimp.exe) which basically takes a path to a type library
and produces a .NET assembly. It has various switches to fine-tune the
conversion process, specify output namespace and also allows the resulting
assembly to be strong named (this is how ESRI's primary interop assemblies we
reference in our projects are produced). There is an <a
href="http://resources.esri.com/help/9.3/ArcGISDesktop/dotnet/concepts_start.htm#12BF6F08-1B25-4002-9640-73D4EB0E6CED.htm">article</a>
on ESRI Developer Network which describes how COM to .NET conversion is done,
and I suspect every ArcObjects developer has at some point read this
through.</p>

<p>So, let's take a look at our IMapSurroundEvents interface. The importer
takes these steps in order to support the event model:</p>

<ul>
	<li>imports the IMapSurroundEvents interface</li>

	<li>for each method in the above interface, creates a <strong>delegate</strong>
	named IMapSurroundE­vents_<em>methodna­me</em>EventHandler, i­e.
		<ul>
			<li>IMapSurroundE­vents_AfterDra­wEventHandler</li>

			<li>IMapSurroundE­vents_BeforeDra­wEventHandler</li>

			<li>IMapSurroundE­vents_ContentsChan­gedEventHandler</li>
		</ul>
	</li>

	<li>creates IMapSurroundsE­vents_Event interface, which contains an event for
	every method (typed by its matching delegate); this interface is used to sink
	COM events in managed code</li>
</ul>

<p>Launching <a href="http://www.red-gate.com/products/reflector/">Reflector</a>
on the ESRI.ArcGIS.Carto assembly, however, reveals two additional items which
are tightly tied to the event interop:</p>

<ul>
	<li>internal sealed class IMapSurroundE­vents_EventPro­vider :
	IMapSurroundE­vents_Event, IDisposable</li>

	<li>public sealed class IMapSurroundE­vents_SinkHel­per :
	IMapSurroundEvents</li>
</ul>

<p>Turns out that the Type Library Importer does some additional work under the
hood. Before we delve into what these two classes are for and why are they
needed, some explanation of the pure COM event model is inevitable.</p>

<h3>Introducing IConnectionPo­intContainer</h3>

<p>First, those who are familiar with COM in C++ and alike can safely skip this
section as it is intended primarily on folks coming from the VB background.
Later on, we will be implementing IConnectionPo­intContainer in .NET.</p>

<p>In COM, an object providing outgoing interfaces implements
<strong>IConnectionPo­intContainer</strong>, which is just one of the few
interfaces supporting COM events. Obviously, the best place to look for detailed
information is the MSDN – see <a
href="http://msdn.microsoft.com/en-us/library/ms694379(VS.85).aspx">Events in
COM and Connectable Objects</a>. Thorough description lies outside the scope of
this post, I'll only present and describe the basic building blocks of this
architecture and their interaction:</p>

<ul>
	<li><a
	href="http://msdn.microsoft.com/en-us/library/ms683857(VS.85).aspx">IConnectionPo­intContainer</a>
	lets the clients know that the object is connectable and provides outgoing
	interfaces. The client is able to find a connection point by specific outgoing
	interface's IID (<a
	href="http://msdn.microsoft.com/en-us/library/ms692476(VS.85).aspx">FindConnecti­onPoint</a>)
	or enumerate over all the available connection points (<a
	href="http://msdn.microsoft.com/en-us/library/ms682460(VS.85).aspx">EnumConnecti­onPoints</a>)).
	That said, map surround objects provide one connection point (for the
	IMapSurroundEvents outgoing interface).</li>

	<li><a
	href="http://msdn.microsoft.com/en-us/library/ms694318(VS.85).aspx">IConnectionPoint</a>
	represents a connection point for a particular outgoing interface. Its purpose
	is to <strong>maintain connections to objects</strong> (sinks) which receive
	events. These sink objects (listeners) implement methods of the outgoing
	interface, which the connectable object (map surround in our case) calls when an
	event arises. Clients register the sink via <a
	href="http://msdn.microsoft.com/en-us/library/ms694318(VS.85).aspx">Advise</a>
	method and unregister by calling <a
	href="http://msdn.microsoft.com/en-us/library/ms686608(VS.85).aspx">Unadvise</a>.
	Advise method takes a reference to the sink object and returns an integer
	identifier (called <strong>cookie</strong>), which is later passed to Unadvise.
	Clients can also examine all „advised“ connections using <a
	href="http://msdn.microsoft.com/en-us/library/ms686608(VS.85).aspx">EnumConnections</a>.</li>
</ul>

<p>So the typical scenario goes along these lines:</p>

<ol>
	<li>The client casts the connectable object to
	<strong>IConnectionPo­intContainer</strong> and uses it
	<strong>FindConnection­Point</strong> method using a particular outgoing
	interface ID (e.g. IMapSurroundEvents' IID). This returns an
	<strong>IConnectionPoint</strong> reference.</li>

	<li>The client uses <strong>Advise</strong> method with the sink object as an
	argument. This object implements the event interface methods. The connectable
	object stores reference to the sink and generates an integer cookie, which then
	client stores.</li>

	<li>Now, when an event is to be raised, the connectable object uses the
	connection point to go through all the connection points. For every connection
	point's sink object, the particular event's method is called.</li>

	<li>Later on, the client unregisters the sink via <strong>Unadvise</strong>
	method, using the <strong>previously stored cookie</strong>.</li>
</ol>

<p>As you see, this is pretty simple, but still involved enough so that a more
convenient way to work with events would come handy. In fact, this is exactly
what good old Visual Basic does – it has the <strong>WithEvents</strong>
keyword which hides this process altogether. In .NET/C#, the effect is very
similar, but it certainly doesn't hurt to see how it's exactly done, as I'll
describe in the next section.</p>

<h3>Sinking COM events in managed code</h3>

<p>With the information from the above section, it becomes clear that .NET has a
bit more work to do than merely importing the necessary interaces – it has to
somehow interlink the COM infrastructure and its delegate/event approach. As you
would now guess, this is exactly where the IMapSurroundE­vents_EventPro­vider
and IMapSurroundE­vents_SinkHel­per classes come into play.</p>

<p>A comprehensive explanation of how these two classes work can be found in a
great MSDN blog post <a
href="http://blogs.msdn.com/varunsekhri/archive/2007/09/18/how-do-we-talk-with-com-the-language-of-events-and-delegates.aspx">How
do we talk with COM the language of events and delegates</a>. While I encourage
you to take a look at the article, I will try to cover the basics since this
will be needed for better understanding the next sections.</p>

<p><strong>IMapSurroundE­vents_SinkHel­per</strong> implements the event
source interface and instances of this class are passed to the aforementioned
<strong>Advise</strong> method. It maintains the generated delegates, which are
invoked upon calling the specific event interface methods (provided the delegate
is not null, i.e., there are some observers subscribing for the event). Sink
helpers are effectively <strong>event sinks</strong> which bridge the COM event
caller and .NET event receiver endpoint.</p>

<p>Now, we need a means to link the event source (IConnectionPo­intContainer)
to the sink helper instances and, in turn, the sink helpers to their event
recipients, which is exactly the job of <em>_EventProvider</em> class. This
class implements the <em>_Event</em> interface (IMapSurroundE­vents_Event),
which as we have seen before contains all the events. Events in .NET are
typically subscribed using the <strong>+=</strong> operator (and unsubscribed
using <strong>=-</strong>) – and that's just about it, you do not need to
maintain the list of subscribers yourself, the compiler does that for you. But
you may happen to know there is also another way – that is, managing the list
of recipients „manually“ through so called <strong>event accessors</strong>.
The need to do so rarely arises in a day-to-day development though, if ever.
This is exactly what the _EventProvider class does – it publishes .NET
events, but does not maintain the list directly – instead, it keeps a list of
the event sink helpers we have talked about. When you use „+=“ in your
program over such event, say IMapSurroundE­vents_Event.Af­terDraw, the event
<strong>add accessor</strong> is called and several things happen behind the
scenes:</p>

<ol>
	<li>The event provider gets and stores the IConnectionPoint from the wrapped
	IConnectionPo­intContainer if this has not been done before (the link to the
	connection point container is provided by the runtime) .</li>

	<li>Creates <strong>new instance</strong> of IMapSurroundE­vents_SinkHel­per
	is created, added to the internal list, and its
	IMapSurroundE­vents_AfterDra­wEventHandler <strong>delegate is set</strong>
	appropriately.</li>

	<li>Calls <strong>Advise</strong> with the newly created sink helper as its
	parameter. In this step, the obtained sink cookie is also stored with the sink
	helper.</li>
</ol>

<p>Using the <strong>-="</strong>operator has analogous effects: the event'
<strong>remove accessor</strong> calls <strong>Unadvise</strong> and removes the
sink helper from the internal list. Apart from this, the event provider also
manages cleanup of objects and COM references (it implements IDisposable).
Details along with code excerpts can be found in the article linked above.</p>

<h3>Events the other way round: exposing .NET events to COM</h3>

<p>Now that we understand how COM events can be propagated to .NET clients, we
can happily rush into solving the opposite: publishing events so that the COM
clients can consume them. This can be fairly easy in some general cases. Say we
have a component which provides an event called <strong>OnDone</strong> fired
after something is, well, done. We also have to define a delegate for the event
(or use one already existing in .NET if it suits our needs). Consider this code
sample:</p>

<pre class="cpp"><code>[ComVisible(<span
class="cpp-keywords1">false</span>)]
<span
class="cpp-keywords1">public</span> delegate <span
class="cpp-keywords1">void</span> OnDoneEventHandler(object sender, EventArgs e);

<span
class="cpp-comment">// Outgoing (source/event) interface.
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid(...)]
public interface ISomeEventInterface
{
    [DispId(1)]
    void OnDone(object sender, EventArgs e);
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(ISomeEventInterface))]
[ProgId(...)]
[Guid(...)]
public class SomeComClass :
{
    public event OnDoneEventHandler OnDone;

    internal void FireOnDone(object sender, EventArgs e)
    {
        if (OnDone != null)
        {
            OnDone(sender, e);
        }
    }
}</span></code></pre>

<p>This is the usual way to publish .NET events to COM, and it works; you can
easily sink the <strong>OnDone</strong> event in VBA/VB6 and other environments.
Both .NET and COM clients are able to subscribe for the event seamlessly.</p>

<h3>IUnknown – the trouble with IMapSurroundEvents</h3>

<p>Too bad we cannot use this approach when implementing our own map surrounds,
and here's why. You may have noticed <em>ISomeEventInter­face</em> is
attributed as <strong>IDispatch</strong>, but the event interface we need to
implement, <em>IMapSurroundE­vents</em>, derives from
<strong>IUnknown</strong>, thus the dispatch mechanism (invoking methods by
their dispatch ids or by their names) cannot be used at all. We obviously do not
have control over ESRI interfaces, we cannot redefine them in any way. So, we'll
have to make do by taking a different path and that is, implementing the
<strong>IConnectionPo­intContainer</strong> and related interfaces all by
ourselves.</p>

<h3>Where are the COM event infrastructure interfaces defined</h3>

<p>Before we implement the necessary interfaces, we need to know where they're
defined along with some structures they use. This depends on your version of
.NET framework. In .NET 2.0 and later, these types are in <a
href="http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.comtypes(VS.80).aspx">System.Runtime­.InteropServi­ces.ComTypes</a>
namespace. In .NET 1.0/1.1 they are directly in
System.Runtime­.InteropServi­ces and are names a bit differently, prefixed
with <em>UCOM</em> (for example <a
href="http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.ucomiconnectionpointcontainer(VS.71).aspx">UCOMIConnecti­onPointContai­ner</a>).
These are marked obsolete in versions from 2.0 up, so from now on I'll be only
referring to the newer versions of these interfaces.</p>

<p>Now, let's look at the <a
href="http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.comtypes.ienumconnections.next(VS.80).aspx">IEnumConnecti­ons.Next</a>
method's signature:</p>

<pre class="cpp"><code><span class="cpp-keywords1">int</span> Next (
    <span
class="cpp-keywords1">int</span> celt,
    [OutAttribute] CONNECTDATA[] rgelt,
    IntPtr pceltFetched
)</code></pre>

<p>This is the main method of the enumerator, it fills the <em>rgelt</em>
CONNECTDATA array (which the client sizes to <em>celt</em> elements prior to
making the call) and stores the actual number of enumerated elements into
<em>pceltFetched</em>. At as far as CONNECTDATA structure is concerned, the only
thing we need to know at this point is that it stores the pointer to the event
sink and the connection cookie.</p>

<p>If we point Reflector at this interface, here's what we will see:</p>

<pre
class="cpp"><code>[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(<span
class="cpp-quote">&quot;B196B287-BAB4-101A-B69C-00AA00341D07&quot;</span>)]
<span
class="cpp-keywords1">public</span> interface IEnumConnections
{
    [PreserveSig]
    <span
class="cpp-keywords1">int</span> Next(<span
class="cpp-keywords1">int</span> celt, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=<span
class="cpp-num">1</span>)] CONNECTDATA[] rgelt, IntPtr pceltFetched);
    [PreserveSig]
    <span
class="cpp-keywords1">int</span> Skip(<span
class="cpp-keywords1">int</span> celt);
    <span
class="cpp-keywords1">void</span> Reset();
    <span
class="cpp-keywords1">void</span> Clone(out IEnumConnections ppenum);
}</code></pre>

<p>Notice the <strong>SizeParamIndex</strong> marshalling parameter. It says
that, during marshalling, the runtime should read the <em>rgelt</em> array size
from parameter at index 1. Quoting the MSDN reference on SizeParamIndex:</p>

<blockquote>
	<p>Indicates which parameter contains the count of array elements, much like
	size_is in COM, and is zero-based.</p>

	<p>From <a
	href="http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshalasattribute.sizeparamindex.aspx">MarshalAsAttri­bute..::.Size­ParamIndex</a></p>
</blockquote>

<p>The documentation clearly states that the index is
<strong>zero-based</strong>, which with the value of <strong>1</strong> points
at the array parameter itself, i.e., it should be <strong>0</strong> instead,
pointing at the <em>celt</em> parameter. Thus I believe the definition in
<strong>mscorlib.dll</strong> is incorrect and indeed, any attempts to use this
method at runtime will result in a marshalling exception to be thrown. Strangely
enough, this bug seems to be introduced in .NET 2.0 because the original (now
obsolete) <em>IUCOM_xxxx</em> interfaces in .NET 1.1 do not appear to be
incorrect:</p>

<pre
class="cpp"><code>[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(<span
class="cpp-quote">&quot;B196B287-BAB4-101A-B69C-00AA00341D07&quot;</span>), Obsolete(<span
class="cpp-quote">&quot;Use System.Runtime.InteropServices.ComTypes.IEnumConnections instead. http://go.microsoft.com/fwlink/?linkid=14202&quot;</span>, <span
class="cpp-keywords1">false</span>)]
<span
class="cpp-keywords1">public</span> interface UCOMIEnumConnections
{
    [PreserveSig]
    <span
class="cpp-keywords1">int</span> Next(<span
class="cpp-keywords1">int</span> celt, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=<span
class="cpp-num">0</span>)] CONNECTDATA[] rgelt, out <span
class="cpp-keywords1">int</span> pceltFetched);
    [PreserveSig]
    <span
class="cpp-keywords1">int</span> Skip(<span
class="cpp-keywords1">int</span> celt);
    [PreserveSig]
    <span
class="cpp-keywords1">void</span> Reset();
    <span
class="cpp-keywords1">void</span> Clone(out UCOMIEnumConnections ppenum);
}</code></pre>

<p>This affects not only <em>IEnumConnecti­ons.Next</em> but also
<em>IEnumConnecti­onPoints.Next</em>, for the very same reason, i.e. the
SizeParamIndex being incorrectly set to 1. I have absolutely no idea how this
bug could be introduced and have not been able to get any feedback on this. (It
is even possible that it is not in fact a bug, just my poor understanding of the
matter. If someone could please confirm or clarify this, I would be more than
grateful.)</p>

<p>There are two possible options to work around this. First, we could use the
obsolete interface imports. Second choice is to redefine the imports correctly
and use our definitions instead of those found in
System.Runtime­.InteropServi­ces.ComTypes. I opt for the latter because I do
not like compiler warnings being spat on me. Also, you can never be sure whether
obsolete classes/interfaces won't disappear in future versions of the framework
without further notice.</p>

<p>Below are the final interface definitions, after being simply taken from
Reflector's output and with the corrections discussed above applied to them.
Because the interfaces are tightly tied together, it is best to redefine all of
them to avoid any unpleasant confusing mix-ups. The only thing we will keep from
the Microsoft's na­mespace is the CONNECTDATA structure.</p>

<pre class="cpp"><code><span
class="cpp-keywords1">namespace</span> MyCOMInterfaceDefinitions
{
    <span
class="cpp-keywords1">using</span> System;
    <span
class="cpp-keywords1">using</span> System.Runtime.InteropServices;
    <span
class="cpp-keywords1">using</span> CONNECTDATA=System.Runtime.InteropServices.ComTypes.CONNECTDATA;

    [ComImport, Guid(<span
class="cpp-quote">&quot;B196B285-BAB4-101A-B69C-00AA00341D07&quot;</span>), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    <span
class="cpp-keywords1">public</span> interface IEnumConnectionPoints
    {
        [PreserveSig]
        <span
class="cpp-keywords1">int</span> Next(<span
class="cpp-keywords1">int</span> celt, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = <span
class="cpp-num">0</span>)] IConnectionPoint[] rgelt, IntPtr pceltFetched);
        [PreserveSig]
        <span
class="cpp-keywords1">int</span> Skip(<span
class="cpp-keywords1">int</span> celt);
        <span
class="cpp-keywords1">void</span> Reset();
        <span
class="cpp-keywords1">void</span> Clone(out IEnumConnectionPoints ppenum);
    }

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(<span
class="cpp-quote">&quot;B196B287-BAB4-101A-B69C-00AA00341D07&quot;</span>)]
    <span
class="cpp-keywords1">public</span> interface IEnumConnections
    {
        [PreserveSig]
        <span
class="cpp-keywords1">int</span> Next(<span
class="cpp-keywords1">int</span> celt, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = <span
class="cpp-num">0</span>)] CONNECTDATA[] rgelt, IntPtr pceltFetched);
        [PreserveSig]
        <span
class="cpp-keywords1">int</span> Skip(<span
class="cpp-keywords1">int</span> celt);
        <span
class="cpp-keywords1">void</span> Reset();
        <span
class="cpp-keywords1">void</span> Clone(out IEnumConnections ppenum);
    }

    [ComImport, Guid(<span
class="cpp-quote">&quot;B196B286-BAB4-101A-B69C-00AA00341D07&quot;</span>), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    <span
class="cpp-keywords1">public</span> interface IConnectionPoint
    {
        <span
class="cpp-keywords1">void</span> GetConnectionInterface(out Guid pIID);
        <span
class="cpp-keywords1">void</span> GetConnectionPointContainer(out IConnectionPointContainer ppCPC);
        <span
class="cpp-keywords1">void</span> Advise([MarshalAs(UnmanagedType.Interface)] object pUnkSink, out <span
class="cpp-keywords1">int</span> pdwCookie);
        <span
class="cpp-keywords1">void</span> Unadvise(<span
class="cpp-keywords1">int</span> dwCookie);
        <span
class="cpp-keywords1">void</span> EnumConnections(out IEnumConnections ppEnum);
    }

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(<span
class="cpp-quote">&quot;B196B284-BAB4-101A-B69C-00AA00341D07&quot;</span>)]
    <span
class="cpp-keywords1">public</span> interface IConnectionPointContainer
    {
        <span
class="cpp-keywords1">void</span> EnumConnectionPoints(out IEnumConnectionPoints ppEnum);
        <span
class="cpp-keywords1">void</span> FindConnectionPoint([In] ref Guid riid, out IConnectionPoint ppCP);
    }
}</code></pre>

<h3>Implementing the interfaces</h3>

<p>With the correctly defined interface imports in place, it's about time to
get our hands dirty, implement them and finally have some fun. I rolled a set
of helper objects which will aid us in supporting the COM events infrastructure.
See below (I'll discuss the code later in detail) :</p>

<pre class="cpp"><code><span
class="cpp-comment">/// &lt;summary&gt;
/// The event container helper. Objects which wish to implement &lt;see cref=&quot;IConnectionPointContainer&quot;/&gt; maintain
/// an instance of this class and delegate all calls to the interface methods to that object.
/// &lt;/summary&gt;
public class EventContainerHelper : IConnectionPointContainer
{
    private readonly IList&lt;eventHelper&gt; eventHelpers = new List&lt;eventHelper&gt;();
    private readonly IDictionary&lt;guid, IConnectionPoint&gt; guidToConnectionPoint = new Dictionary&lt;guid, IConnectionPoint&gt;();
    private readonly ConnectionPointList connectionPoints = new ConnectionPointList();
    private readonly IConnectionPointContainer connectionPointContainer;

    /// &lt;summary&gt;
    /// Creates a new instance of the event container helper.
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;connectionPointContainer&quot;&gt;The connection point container. All calls to that connection
    /// point container are to be delegated to this newly created instance of &lt;see cref=&quot;NetComEventsTest.EventContainerHelper&quot;/&gt;.
    /// &lt;/param&gt;
    public EventContainerHelper(IConnectionPointContainer connectionPointContainer)
    {
        if (connectionPointContainer == null) throw new ArgumentNullException(&quot;connectionPointContainer&quot;);
        this.connectionPointContainer = connectionPointContainer;
    }

    /// &lt;summary&gt;
    /// Adds a new event interface to which this helper should react.
    /// &lt;/summary&gt;
    /// &lt;typeparam name=&quot;NETEventInterface&quot;&gt;The .NET event interface which the type library importer
    /// creates for a COM event source interface.&lt;/typeparam&gt;
    /// &lt;returns&gt;An event helper which can be used to raise events on the specified event interface.&lt;/returns&gt;
    public EventHelper&lt;neteventInterface&gt; AddEvents&lt;neteventInterface&gt;()
    {
        EventHelper&lt;neteventInterface&gt; eventHelper =
            new EventHelper&lt;neteventInterface&gt;(connectionPointContainer);
        eventHelpers.Add(eventHelper);
        guidToConnectionPoint.Add(eventHelper.ComEventInterfaceType.GUID, eventHelper);
        connectionPoints.Add(eventHelper);

        return eventHelper;
    }

    #region Implementation of IConnectionPointContainer

    public void EnumConnectionPoints(out IEnumConnectionPoints ppEnum)
    {
        ppEnum = connectionPoints;
    }

    public void FindConnectionPoint(ref Guid riid, out IConnectionPoint ppCP)
    {
        ppCP = guidToConnectionPoint.ContainsKey(riid) ? guidToConnectionPoint[riid] : null;
    }

    #endregion
}

/// &lt;summary&gt;
/// The list of connection points. This class is used in &lt;see cref=&quot;EventContainerHelper&quot;/&gt; and serves
/// merely to implement the &lt;see cref=&quot;IEnumConnectionPoints&quot;/&gt; interface.
/// &lt;/summary&gt;
internal class ConnectionPointList : IEnumConnectionPoints
{
    private IList&lt;iconnectionPoint&gt; connectionPoints = new List&lt;iconnectionPoint&gt;();
    private int currentEnumIndex;

    /// &lt;summary&gt;
    /// Adds a connection point to the list.
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;connectionPoint&quot;&gt;The connection point.&lt;/param&gt;
    public void Add(IConnectionPoint connectionPoint)
    {
        if (connectionPoint == null) throw new ArgumentNullException(&quot;connectionPoint&quot;);
        connectionPoints.Add(connectionPoint);
    }

    #region Implementation of IEnumConnectionPoints

    public int Next(int celt, IConnectionPoint[] rgelt, out int pceltFetched)
    {
        int fetched = 0;
        for (int i = currentEnumIndex; i &lt; connectionPoints.Count; i++)
        {
            rgelt[fetched] = connectionPoints[i];
            fetched = fetched + 1;
            if (fetched == celt) break;
        }
        currentEnumIndex = currentEnumIndex + fetched;
        pceltFetched = fetched;

        return fetched == celt ? 0 : 1;
    }

    public int Skip(int celt)
    {
        currentEnumIndex += celt;
        return currentEnumIndex &lt; connectionPoints.Count ? 0 : 1;
    }

    public void Reset()
    {
        currentEnumIndex = 0;
    }

    public void Clone(out IEnumConnectionPoints ppenum)
    {
        ConnectionPointList clone = new ConnectionPointList();
        clone.connectionPoints = connectionPoints;
        clone.currentEnumIndex = currentEnumIndex;
        ppenum = clone;
    }

    #endregion

}

/// &lt;summary&gt;
/// Base event helper class.
/// &lt;/summary&gt;
public abstract class EventHelper
{

}

/// &lt;summary&gt;
/// The event helper class. This class aids in publishing .NET events to COM via connection points
/// infrastructure.
/// &lt;/summary&gt;
/// &lt;typeparam name=&quot;NETEventInterface&quot;&gt;The .NET event interface (associated with a COM event source interface)
/// created by the type library  importer.&lt;/typeparam&gt;
public class EventHelper&lt;neteventInterface&gt; : EventHelper, IConnectionPoint
{
    private readonly IDictionary&lt;type, MethodInfo&gt; delegatesToMethods = new Dictionary&lt;type, MethodInfo&gt;();
    private readonly ConnectionList observers = new ConnectionList();
    private readonly IConnectionPointContainer connectionPointContainer;
    private readonly Type comEventInterfaceType;

    /// &lt;summary&gt;
    /// Creates a new instance of the event helper class.
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;connectionPointContainer&quot;&gt;The connection point container.&lt;/param&gt;
    public EventHelper(IConnectionPointContainer connectionPointContainer)
    {
        if (connectionPointContainer == null) throw new ArgumentNullException(&quot;connectionPointContainer&quot;);

        // find the COM event interface associated with the NET event interface
        Type netEventsType = typeof(NETEventInterface);

        foreach (object attribute in netEventsType.GetCustomAttributes(typeof(ComEventInterfaceAttribute), false))
        {
            ComEventInterfaceAttribute comEventInterfaceAttribute = (ComEventInterfaceAttribute)attribute;
            comEventInterfaceType = comEventInterfaceAttribute.SourceInterface;
            break;
        }

        if (comEventInterfaceType == null)
        {
            throw new ArgumentException(&quot;The type parameter is not a .NET event interface corresponding to a COM event interface.&quot;);
        }

        foreach (MethodInfo methodInfo in comEventInterfaceType.GetMethods())
        {
            EventInfo eventInfo = netEventsType.GetEvent(methodInfo.Name);
            if (eventInfo == null || eventInfo.EventHandlerType == null) continue;
            delegatesToMethods.Add(eventInfo.EventHandlerType, methodInfo);
        }
        this.connectionPointContainer = connectionPointContainer;
    }

    /// &lt;summary&gt;
    /// The COM event source interface associated with the .NET event interface which was
    /// specified as the type parameter.
    /// &lt;/summary&gt;
    public Type ComEventInterfaceType
    {
        get { return comEventInterfaceType; }
    }

    /// &lt;summary&gt;
    /// Raises a COM event which COM clients can consume. The number and type of parameters
    /// specified in &lt;paramref name=&quot;args&quot;/&gt; must exactly match the event method parameters.
    /// &lt;/summary&gt;
    /// &lt;typeparam name=&quot;EventDelegate&quot;&gt;The event delegate which the type library importer created
    /// for the COM event which you want to raise.&lt;/typeparam&gt;
    /// &lt;param name=&quot;args&quot;&gt;COM event method arguments. Their number and type must match exactly.&lt;/param&gt;
    public void Raise&lt;eventDelegate&gt;(params object[] args)
    {
        if (!delegatesToMethods.ContainsKey(typeof(EventDelegate))) return;
        MethodInfo methodInfo = delegatesToMethods[typeof(EventDelegate)];
        foreach (object obj in observers.Connections)
        methodInfo.Invoke(obj, args);
    }

    #region IConnectionPoint Members

    void IConnectionPoint.GetConnectionInterface(out Guid pIID)
    {
        pIID = comEventInterfaceType.GUID;
    }

    void IConnectionPoint.GetConnectionPointContainer(out IConnectionPointContainer ppCPC)
    {
        ppCPC = connectionPointContainer;
    }

    void IConnectionPoint.Advise(object pUnkSink, out int pdwCookie)
    {
        pdwCookie = observers.Add(pUnkSink);
    }

    void IConnectionPoint.Unadvise(int dwCookie)
    {
        observers.Remove(dwCookie);
    }

    void IConnectionPoint.EnumConnections(out IEnumConnections ppEnum)
    {
        ppEnum = observers;
    }

    #endregion
}

/// &lt;summary&gt;
/// The connection list. This class is used in &lt;see cref=&quot;EventHelper{NETEventInterface}&quot;/&gt;s and it maintains
/// list of connections and their cookies.
/// &lt;/summary&gt;
internal class ConnectionList : IEnumConnections
{
    private IList&lt;keyValuePair&lt;int, object&gt;&gt; connections = new List&lt;keyValuePair&lt;int, object&gt;&gt;();
    private int currentCookie;
    private int currentEnumIndex;

    /// &lt;summary&gt;
    /// Adds an object (event sink) to the list.
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;obj&quot;&gt;Object.&lt;/param&gt;
    /// &lt;returns&gt;The object's cookie which can be later used in the &lt;see cref=&quot;Remove&quot;/&gt; method.&lt;/returns&gt;
    public int Add(object obj)
    {
        currentCookie++;
        connections.Add(new KeyValuePair&lt;int, object&gt;(currentCookie, obj));
        return currentCookie;
    }

    /// &lt;summary&gt;
    /// Removes an object from the list.
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;cookie&quot;&gt;The objects cookie previously returned from the &lt;see cref=&quot;Add&quot;/&gt; method.&lt;/param&gt;
    public void Remove(int cookie)
    {
        for (int i = 0; i &lt; connections.Count; i++)
        {
            if (connections[i].Key == cookie)
            {
                connections.RemoveAt(i);
                return;
            }
        }
    }

    /// &lt;summary&gt;
    /// The enumeration of connection objects.
    /// &lt;/summary&gt;
    public IEnumerable&lt;object&gt; Connections
    {
        get
        {
            foreach (KeyValuePair&lt;int, object&gt; pair in connections)
            {
                yield return pair.Value;
            }
        }
    }

    #region Implementation of IEnumConnections

    public int Next(int celt, CONNECTDATA[] rgelt, IntPtr pceltFetched)
    {
        int fetched = 0;
        for (int i = currentEnumIndex; i &lt; connections.Count; i++)
        {
            CONNECTDATA connectData = new CONNECTDATA();
            connectData.dwCookie = connections[i].Key;
            connectData.pUnk = connections[i].Value;
            rgelt[fetched] = connectData;
            fetched = fetched + 1;
            if (fetched == celt) break;
        }
        currentEnumIndex = currentEnumIndex + fetched;
        if (pceltFetched != IntPtr.Zero)
        {
            Marshal.WriteInt32(pceltFetched, fetched);
        }
        return fetched == celt ? 0 : 1;
    }

    public int Skip(int celt)
    {
        currentEnumIndex += celt;
        return currentEnumIndex &lt; connections.Count ? 0 : 1;
    }

    public void Reset()
    {
        currentEnumIndex = 0;
    }

    public void Clone(out IEnumConnections ppenum)
    {
        ConnectionList clone = new ConnectionList();
        clone.connections = connections;
        clone.currentCookie = currentCookie;
        clone.currentEnumIndex = currentEnumIndex;
        ppenum = clone;
    }

    #endregion
}</span></code></pre>

<p>Do not be scared by its length, once you get the gist of it, it becomes
pretty straightforward. The good thing about this code is that it can be used in
a very generic fashion, i.e. whenever you want to expose COM events in a .NET
object. The bad thing is that this solution does not play well with standard
.NET events, which is not, however, the focus of this little article.</p>

<h3>Putting it together</h3>

<p>Now, let's show how these helper classes are used:</p>

<pre
class="cpp"><code>[ComSourceInterfaces(typeof(IMapSurroundEvents))]
[ProgId(<span
class="cpp-quote">&quot;...&quot;</span>)]
[Guid(<span
class="cpp-quote">&quot;...&quot;</span>)]
[ComVisible(<span
class="cpp-keywords1">true</span>)]
[ClassInterface(ClassInterfaceType.None)]
<span
class="cpp-keywords1">public</span> <span
class="cpp-keywords1">class</span> MyMapSurround : IConnectionPointContainer, IMapSurround, IBoundsProperties, IClone, IPersistVariant
{
    <span
class="cpp-keywords1">private</span> readonly EventContainerHelper eventContainerHelper;
    <span
class="cpp-keywords1">private</span> readonly EventHelper&lt;imapSurroundEvents_Event&gt; eventHelper;

    <span
class="cpp-keywords1">public</span> MyMapSurround()
    {
        eventContainerHelper = <span
class="cpp-keywords1">new</span> EventContainerHelper(<span
class="cpp-keywords1">this</span>);
        eventHelper = eventContainerHelper.AddEvents&lt;imapSurroundEvents_Event&gt;();
    }

    <span
class="cpp-preproc">#region IConnectionPointContainer Members

    public void EnumConnectionPoints(out IEnumConnectionPoints ppEnum)
    {
        eventContainerHelper.EnumConnectionPoints(out ppEnum);
    }

    public void FindConnectionPoint(ref Guid riid, out IConnectionPoint ppCP)
    {
        eventContainerHelper.FindConnectionPoint(ref riid, out ppCP);
    }

    #endregion

    // This is the IMapSurround.Draw method.
    public void Draw(IDisplay display, ITrackCancel trackCancel, IEnvelope bounds)
    {
        eventHelper.Raise&lt;imapSurroundEvents_BeforeDrawEventHandler&gt;(display);
        // do some drawing
        eventHelper.Raise&lt;imapSurroundEvents_AfterDrawEventHandler&gt;(display);
    }

    // ... other IMapSurround methods as well as other implemented interface methods</span></code></pre>

<p>Basically, these are the essential steps:</p>

<ol>
	<li>Create an instance of the <strong>EventContainer­Helper</strong> class and
	use its <strong>AddEvents</strong> method to tell it the event interface which
	you want to support. You specify the imported .NET event interface, the method
	will determine the associated original COM event source interface automatically
	using reflection.</li>

	<li>Implement <strong>IConnectionPo­intContainer</strong> on your map surround
	object, and delegate all calls on this interface methods to your instance of
	<strong>EventContainer­Helper</strong>. As you have specified which event
	interfaces you want to support by calling <strong>AddEvents</strong>, this
	object knows how to respond to <strong>FindConnection­Point</strong> when it is
	called by a COM client (i.e. ArcMap in our case).</li>

	<li>The <strong>AddEvents</strong> method hands you over an instance of
	<strong>EventHelper</strong> class, which you can later on use to raise the
	events, as shown in the example implementation of map surround's Draw method.
	The important bit to keep in mind here is that the parameters you pass to the
	<strong>Raise</strong> method must exactly match the parameters of the event
	delegate you specify as the type parameter.</li>
</ol>

<p>When you add your map surround object to the ArcMap's page layout, ArcMap
will immediately subscribe for these events, providing its event sinks,
listening and waiting for you to raise the events. Most importantly, when you
implement a map surround of your own, do not forget to raise
<strong>ContentsChanged</strong> event, which causes you object to redraw
correctly (i.e., ArcMap will call your <strong>Draw</strong> implementation if
it needs to).</p>

<h3>The end</h3>

<p>Now, I will leave you to examine the code. If you do not understand its
inner workings, I suggest you roll your own IMapSurround implementation, use
the helper objects as shown, and go through the code step by step, for example
using a debbuger. It will suddenly come clear what the code does and how ArcMap
interacts with your object and its events.</p>

<p>Conversely, if you have any question or (even better) suggestions, feel free
to express yourself in the comments below. ANY corrections and suggestions are
more than welcome!</p>

<!-- by Texy2! -->]]></content:encoded>
			<wfw:commentRss>http://www.tydlevidle.cz/2009/03/exposing-com-events-from-net-implementing-mapsurround-in-arcmap/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Mapy.cz v OpenLayers</title>
		<link>http://www.tydlevidle.cz/2008/11/mapycz-v-openlayers/</link>
		<comments>http://www.tydlevidle.cz/2008/11/mapycz-v-openlayers/#comments</comments>
		<pubDate>Sat, 01 Nov 2008 02:36:41 +0000</pubDate>
		<dc:creator>petr k.</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[gis]]></category>
		<category><![CDATA[mapy.cz]]></category>
		<category><![CDATA[openlayers]]></category>
		<category><![CDATA[projekce]]></category>
		<category><![CDATA[seznam]]></category>

		<guid isPermaLink="false">http://www.tydlevidle.cz/?p=9</guid>
		<description><![CDATA[<p>Pokud jste se někdy zamýšleli nad tím, jakým způsobem lze použít mapy<br />
Seznamu (aka mapy.cz) v opensource knihovně<br />
OpenLayers, možná jste přišli na to, že<br />
to není tak jednoduché, jak se na první pohled zdá. Seznam sice poskytuje ke<br />
svým mapám API, ale to je, ačkoliv vcelku<br />
povedené, co do možností poněkud omezené oproti OpenLayers. </p>
<p>Souřadný systém</p>
<p>Základním problémem je ovšem Seznamem používaný [...]</p>
]]></description>
			<content:encoded><![CDATA[
<p>Pokud jste se někdy zamýšleli nad tím, jakým způsobem lze použít mapy
Seznamu (aka <a href="http://www.mapy.cz">mapy.cz</a>) v opensource knihovně
<a href="http://openlayers.org">OpenLayers</a>, možná jste přišli na to, že
to není tak jednoduché, jak se na první pohled zdá. Seznam sice poskytuje ke
svým mapám <a href="http://api.mapy.cz">API</a>, ale to je, ačkoliv vcelku
povedené, co do možností poněkud omezené oproti OpenLayers. <span
id="more-9"></span></p>

<h3>Souřadný systém</h3>

<p>Základním problémem je ovšem Seznamem používaný souřadný systém.
Jak je uvedeno <a
href="http://mailman.fsv.cvut.cz/pipermail/freegeocz/2008-April/000325.html">zde</a>,
jedná se o mírně upravenou projekci WGS84/UTM v zóně 33. Netuším,
proč Seznam zmíněné zobrazení upravuje k obrazu svému; možná to má
souvislost s rozdělením zájmového území do dlaždic (a systémem jejich
číslování), možná ne <img
src="http://www.tydlevidle.cz/wp-includes/images/smilies/icon_smile.gif"
alt=":-)" class="smiley" /> Tak či onak, Google a podobné služby používají
dlaždice i souřadný systém rozdílně, což překryv dat ze Seznamu a
těchto služeb prakticky znemožnuje. Ještě dlužno podotknout, že
formální definice projekce, na kterou zmíněný článek odkazuje (najdete ji
na <a
href="http://www.spatialreference.org/ref/user/mapycz-projection/">http://www.spatialreference.org/…-projection/</a>),
mi bohužel nedávala správné výsledky. Testoval jsem v prostředí ESRI,
kde jsem programově (ArcObjects) vytvořil kýženou transformaci pomocí WKT
popisu a převedené souřadnic porovnával s tím, co ukazovalo webové
prostředí Mapy.cz. Zda je chyba na mé straně, na straně autora definice
souř. systému, či „na straně Seznamu“ (souř. systém se mohl od té
doby změnit), netuším.</p>

<h3>OpenLayers.La­yer.Seznam</h3>

<p>Nicméně, chceme-li pouze data v prostředí OpenLayers zobrazit a potíže
s integrací dat jiných služeb nás tolik netrápí, žádný závažnější
problém již v podstatě neexistuje. Soubor <a
href="http://www.tydlevidle.cz/files/seznam.js">seznam.js</a> obsahuje třídu
<em>OpenLayers.La­yer.Seznam</em>, která data umí zobrazit. (Poznámka:
zdrojový kód chápejte jen jako <strong>čistě testovací a
demonstrační</strong>). Kód je životně závislý na předpokladu, že
Seznam stahování dlaždic přímým URL ze svých nijak neomezuje, což je
premisa v době psaní tohoto článku pravdivá. V budoucnosti lze ale podle
mého názoru očekávat v tomto ohledu nějakou změnu <img
src="http://www.tydlevidle.cz/wp-includes/images/smilies/icon_smile.gif"
alt=":-)" class="smiley" /> Základní příklad použití kódu je zde:</p>

<pre class="js"><code><span
class="js-comment">// volano z onload
function initOpenLayersMap() {
    var map = new OpenLayers.Map('map');  // predpokladame div s id &quot;map&quot;
    var seznamLyr = new OpenLayers.Layer.Seznam(&quot;Seznam&quot;);
    map.addLayers([seznamLyr]);

    map.addControl(new OpenLayers.Control.LayerSwitcher());
    map.addControl(new OpenLayers.Control.MousePosition());

    map.setCenter(new OpenLayers.LonLat(134801968, 138286528), 7);
}</span></code></pre>

<p>Všimněte si toho, že se zde tedy souřadnice v metodě
<code>setCenter</code> uvádějí v souřadném systému Seznamu (tyto jsou
zhruba v oblasti Liberce a Jablonce nad Nisou), nikoliv v souřadnicích
zeměpisných.</p>

<h3>Overlay mapy</h3>

<p>Další příklad ukazuje využití jiné podkladové mapy a též
demonstruje přidání další překryvné vrstvy:</p>

<pre class="js"><code><span class="js-keywords1">function</span><span
class="js-out"> initOpenLayersMap() {
    </span><span
class="js-keywords1">var</span><span class="js-out"> map = </span><span
class="js-keywords1">new</span><span class="js-out"> OpenLayers.Map(</span><span
class="js-quote">'map'</span><span class="js-out">);
    </span><span
class="js-keywords1">var</span><span class="js-out"> seznamLyr = </span><span
class="js-keywords1">new</span><span
class="js-out"> OpenLayers.Layer.Seznam(</span><span
class="js-quote">&quot;Seznam&quot;</span><span class="js-out">, </span><span
class="js-quote">&quot;ophoto&quot;</span><span
class="js-out">);

    </span><span class="js-keywords1">var</span><span
class="js-out"> popisyLyr = </span><span class="js-keywords1">new</span><span
class="js-out"> OpenLayers.Layer.Seznam(</span><span
class="js-quote">&quot;Popisy&quot;</span><span class="js-out">, </span><span
class="js-quote">&quot;hybrid&quot;</span><span
class="js-out">);
    popisyLyr.isBaseLayer = </span><span
class="js-keywords1">false</span><span class="js-out">;

    </span><span
class="js-keywords1">var</span><span
class="js-out"> turistTrasyLyr = </span><span
class="js-keywords1">new</span><span
class="js-out"> OpenLayers.Layer.Seznam(</span><span
class="js-quote">&quot;Turistické trasy&quot;</span><span
class="js-out">, </span><span class="js-quote">&quot;ttur&quot;</span><span
class="js-out">);
    turistTrasyLyr.isBaseLayer = </span><span
class="js-keywords1">false</span><span
class="js-out">;

    map.addLayers([seznamLyr, popisyLyr, turistTrasyLyr]);

    map.addControl(</span><span
class="js-keywords1">new</span><span
class="js-out"> OpenLayers.Control.LayerSwitcher());
    map.addControl(</span><span
class="js-keywords1">new</span><span
class="js-out"> OpenLayers.Control.MousePosition());

    map.setCenter(</span><span
class="js-keywords1">new</span><span
class="js-out"> OpenLayers.LonLat(</span><span
class="js-num">134801968</span><span class="js-out">, </span><span
class="js-num">138286528</span><span class="js-out">), </span><span
class="js-num">10</span><span class="js-out">);
}</span></code></pre>

<p>Výsledek by mohl vypadat nějak takto (ortofoto mapa s popisy a
turistickými trasami):</p>

<div><a
href="http://www.tydlevidle.cz/wp-content/mapycz-base-with-overlay.jpg"><img
src="http://www.tydlevidle.cz/wp-content/mapycz-base-with-overlay.jpg"
alt="Podkladová mapa s překryvnými rastry" title="mapycz-base-with-overlay"
width="500" height="281" class="aligncenter size-full wp-image-11" /></a></div>

<p>Všimněte si parametru následujícího název vrstvy. Ten udává zdroj dat
na serverech Seznamu a přímo se promítne do URL stahovaných dlaždic. Jako
podkladové mapy lze použít:</p>

<ul>
	<li><strong>base</strong> základní mapa (nemusí se uvádět)</li>

	<li><strong>turist</strong> turistická mapa</li>

	<li><strong>ophoto</strong> ortofotomapa</li>

	<li><strong>army2</strong> historická 1836–52</li>

	<li><strong>ophoto0203</strong> ortofotomapa 2002–3</li>
</ul>

<p>Jako překryvné mapy pak můžete specifikovat:</p>

<ul>
	<li><strong>hybrid</strong> popisy (sídel, silnic ap.)</li>

	<li><strong>relief-h</strong> stínování</li>

	<li><strong>tturist</strong> turistické trasy</li>

	<li><strong>tcyklo</strong> cyklostezky</li>
</ul>

<p>Pochopitelně ne všechny kombinace mají smysl a ne všechna data jsou
dostupná ve všech měřítcích. Je potřeba s tím trochu
experimentovat <img
src="http://www.tydlevidle.cz/wp-includes/images/smilies/icon_wink.gif"
alt=";-)" class="smiley" /></p>

<h3>Zeměpisné souřadnice</h3>

<p>Výše uvedené je samo o sobě hezké, ale bez alespoň minimální podpory
uživateli blízkých zeměpisných souřadnic to, přiznejme si, není to
pravé ořechové. Při pohybu kurzorem nad mapou se prvek
<em>OpenLayers.Con­trol.MousePosi­tion</em> o zobrazení souřadnic postará,
nicméně se pochopitelně jedná o souřadnice zdrojových dat. Proto jsem
též vedle třídy samotné vrstvy připojil i požadované zobrazení pro
Mapy.cz – třída <em>OpenLayers.Pro­jection.Seznam</em>. Umí převod z a
do WGS84 (EPSG kód 4326). Toto zobrazení je „seznamáckým“ vrstvám
přiřazeno automaticky, stačí tedy jen adekvátně specifikovat projekci, se
kterou mají pracovat k mapě připojené ovládací prvky. Důležité je, že
toto se musí stát ještě před přidáním ovl. prvků do mapy. Konkrétní
příklad viz níže <img
src="http://www.tydlevidle.cz/wp-includes/images/smilies/icon_smile.gif"
alt=":-)" class="smiley" /> :</p>

<pre class="js"><code><span class="js-keywords1">function</span><span
class="js-out"> initOpenLayersMap() {
    </span><span
class="js-keywords1">var</span><span class="js-out"> map = </span><span
class="js-keywords1">new</span><span class="js-out"> OpenLayers.Map(</span><span
class="js-quote">'map'</span><span class="js-out">);
    </span><span
class="js-keywords1">var</span><span class="js-out"> seznamLyr = </span><span
class="js-keywords1">new</span><span
class="js-out"> OpenLayers.Layer.Seznam(</span><span
class="js-quote">&quot;Seznam&quot;</span><span
class="js-out">);
    map.addLayers([seznamLyr]);

    map.projection = </span><span
class="js-keywords1">new</span><span
class="js-out"> OpenLayers.Projection(</span><span
class="js-quote">&quot;EPSG:4326&quot;</span><span
class="js-out">);
    map.displayProjection = map.projection;

    map.addControl(</span><span
class="js-keywords1">new</span><span
class="js-out"> OpenLayers.Control.LayerSwitcher());
    map.addControl(</span><span
class="js-keywords1">new</span><span
class="js-out"> OpenLayers.Control.MousePosition());

    map.setCenter(</span><span
class="js-keywords1">new</span><span
class="js-out"> OpenLayers.LonLat(</span><span
class="js-num">134801968</span><span class="js-out">, </span><span
class="js-num">138286528</span><span class="js-out">), </span><span
class="js-num">7</span><span class="js-out">);
}</span></code></pre>

<h3>Ende</h3>

<p>Pevně věřím, že bude kód někomu užitečný. I tak ho prosím ale
berte s rezervou, není pochopitelně nijak oficiálně spojen se Seznamem
samotným, ani s poskytovateli relevantních dat. Může se snadno stát, že
jakákoliv změna ze strany Seznamu bude mít za následek jeho nefunkčnost.
Také bych chtěl upozornit na to, že v žádném případě nejsem expert na
OpenLayers, proto patrně budou některé věci neošetřené a některé jsou
naopak ošetřované dost možná zbytečně. Seeya.</p>

<!-- by Texy2! -->]]></content:encoded>
			<wfw:commentRss>http://www.tydlevidle.cz/2008/11/mapycz-v-openlayers/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Coffee art</title>
		<link>http://www.tydlevidle.cz/2008/05/coffee-art/</link>
		<comments>http://www.tydlevidle.cz/2008/05/coffee-art/#comments</comments>
		<pubDate>Thu, 08 May 2008 13:56:43 +0000</pubDate>
		<dc:creator>petr k.</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[art]]></category>

		<guid isPermaLink="false">http://www.tydlevidle.cz/?p=7</guid>
		<description><![CDATA[<p>V souvislosti s výstavou Podoby<br />
současné kartografie na Přírodovědecké fakultě UK, kterou jsem zatím<br />
neměl možnost navštívit, jsem si vzpomněl na tuhle záležitost:</p>
<p>Tohle by byl exponát! Otázka je, který ze studentů nebo členů<br />
katedry by byl ochoten jej udržovat. Též by zažil těžké časy při<br />
odpovídání na otázku, ve kterém kartografickém zobrazení mapa je,<br />
zvlášť když uvážíme, že bublinky v kávové pěně rozhodně [...]</p>
]]></description>
			<content:encoded><![CDATA[
<p>V souvislosti s výstavou <a
href="http://web.natur.cuni.cz/gis/index.php?option=com_content&amp;task=view&amp;id=551&amp;Itemid=128">Podoby
současné kartografie</a> na Přírodovědecké fakultě UK, kterou jsem zatím
neměl možnost navštívit, jsem si vzpomněl na tuhle záležitost:</p>

<div title="kávová mapa světa" style="text-align:center"><img
src="//hosting/www/tydlevidle.cz/www/wp-content/uploads/coffee.jpg" alt=""
/></div>

<p><em>Tohle</em> by byl exponát! Otázka je, který ze studentů nebo členů
katedry by byl ochoten jej udržovat. Též by zažil těžké časy při
odpovídání na otázku, ve kterém kartografickém zobrazení mapa je,
zvlášť když uvážíme, že bublinky v kávové pěně rozhodně na jednom
místě nepostávají.</p>

<p>Na další povedené kousky, včetně pár instruktážních videí, lze
mrknout <a
href="http://www.pantherhouse.com/newshelton/espresso/">zde</a> .</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tydlevidle.cz/2008/05/coffee-art/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>GIS DSL</title>
		<link>http://www.tydlevidle.cz/2008/05/gis-dsl/</link>
		<comments>http://www.tydlevidle.cz/2008/05/gis-dsl/#comments</comments>
		<pubDate>Fri, 02 May 2008 13:19:05 +0000</pubDate>
		<dc:creator>petr k.</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[dsl]]></category>
		<category><![CDATA[gis]]></category>

		<guid isPermaLink="false">http://www.tydlevidle.cz/?p=6</guid>
		<description><![CDATA[<p>Zajímavou věcí, i když nijak novou, jsou tzv. doménově specifické<br />
jazyky (znáte někdo lepší překlad?). Dobrý a docela podrobný popis pojmu<br />
lze nalézt např. na Wikipedii,<br />
kratší a obecnější vysvětlení pak na root.cz.<br />
Co je zvláštní, že v oblasti GIS v podstatě žádné produkty ničím<br />
takovým vybaveny nejsou. Nebylo by fajn moci vyjádřit se např. tímto<br />
způsobem:</p>
<p>Dataset ds = open &#8220;folder://d:/data/shapefiles/&#8221;;<br />
Features lesy = ds [...]</p>
]]></description>
			<content:encoded><![CDATA[
<p>Zajímavou věcí, i když nijak novou, jsou tzv. doménově specifické
jazyky (znáte někdo lepší překlad?). Dobrý a docela podrobný popis pojmu
lze nalézt např. na <a
href="http://en.wikipedia.org/wiki/Domain-specific_programming_language">Wikipedii</a>,
kratší a obecnější vysvětlení pak na <a
href="http://www.root.cz/clanky/domain-specific-language-programovani-pro-kazdeho/">root.cz</a>.
Co je zvláštní, že v oblasti GIS v podstatě žádné produkty ničím
takovým vybaveny nejsou. Nebylo by fajn moci vyjádřit se např. tímto
způsobem:</p>

<pre>
Dataset ds = open "folder://d:/data/shapefiles/";
Features lesy = ds open "lesy.shp";
Features silnice = ds open "silnice.shp";

Features silnice1a2tridy = query silnice {
   "TRIDA" in (1, 2)
}

Features vyberLesu = query lesy {
   area(.) &gt; 5km2 and . intersects buffer(silnice1a2tridy, 100m)
}

double celkovaRozloha = 0;
foreach(Feature les in vyberLesu) {
  celkovaRozloha += area(les);
}
</pre>

<p>Výše uvedený pseudokód měl zjistit celkovou plochu lesů s výměrou
nad 5km<sup>2</sup>, které leží do vzdálenosti 100m od silnic 1. a
2. třídy. Kdybychom podobnou záležitost měli napsat např. v ArcObjects,
vyšlo by to určitě minimálně na 50 řádků kódu (spíš víc). Jistě,
ArcGIS od ESRI poskytuje <strong>geoprocessing</strong> a na něj vázaný
<strong>ModelBuilder</strong>, což v podstatě <em>je</em> grafická forma
DSL, ale ten zase na druhou stranu nenabízí takovou kontrolu nad jednotlivými
prvky a daty tak, jak bychom často potřebovali (i když výše popsaný
příklad by se pomocí něj vzhledem k jednoduchosti dal popsat velice
snadno). Geoprocessing v ESRI pomocí Pythonu je už tomuto cíli o něco
blíže, ale stále je pro tento účel poněkud těžkopádný, jeho výhoda
ovšem leží v dostupnosti široké škály modulů.</p>

<p>Potenciál je veliký – představme si jednotný jazyk nad různými
systémy (ESRI, grass, …). Jistě, implementace něčeho takového by určitě
byla oříšek, překvapující je snad jen to, že žádný takový projekt
v současnosti neexistuje (rád bych se mýlil).</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tydlevidle.cz/2008/05/gis-dsl/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

<!-- Dynamic Page Served (once) in 0.182 seconds -->
