Full Code of jagodki/Offline-MapMatching for AI

master 0e345da5e241 cached
37 files
888.3 KB
315.4k tokens
117 symbols
1 requests
Download .txt
Showing preview only (917K chars total). Download the full file or copy to clipboard to get everything.
Repository: jagodki/Offline-MapMatching
Branch: master
Commit: 0e345da5e241
Files: 37
Total size: 888.3 KB

Directory structure:
gitextract_54u4k5gb/

├── .gitignore
├── LICENSE
├── README.md
├── src/
│   └── offlinemapmatching/
│       ├── __init__.py
│       ├── help_docs/
│       │   ├── help.html
│       │   ├── help_processing_clipping_network.html
│       │   ├── help_processing_match_trajectory.html
│       │   └── help_processing_reduce_density.html
│       ├── i18n/
│       │   └── af.ts
│       ├── metadata.txt
│       ├── mm/
│       │   ├── __init__.py
│       │   ├── helper/
│       │   │   ├── __init__.py
│       │   │   └── measurement_statistics.py
│       │   ├── hidden_states/
│       │   │   ├── __init__.py
│       │   │   ├── candidate.py
│       │   │   ├── hidden_model.py
│       │   │   └── transition.py
│       │   ├── map_matcher.py
│       │   └── observation/
│       │       ├── __init__.py
│       │       ├── intersection.py
│       │       ├── network.py
│       │       ├── observation.py
│       │       └── trajectory.py
│       ├── mm_processing/
│       │   ├── clip_network_algorithm.py
│       │   ├── offline_map_matching_algorithm.py
│       │   ├── offline_map_matching_provider.py
│       │   └── reduce_trajectory_density.py
│       ├── offline_map_matching.py
│       ├── offline_map_matching_dialog.py
│       ├── offline_map_matching_dialog_base.ui
│       ├── pb_tool.cfg
│       ├── resources.py
│       ├── resources.qrc
│       └── style.qml
└── testdata/
    ├── network.geojson
    ├── trajectory1.geojson
    └── trajectory2.geojson

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Build and Release Folders
bin-debug/
bin-release/
[Oo]bj/
[Bb]in/

# Other files and folders
.settings/

# Executables
*.swf
*.air
*.ipa
*.apk

# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
# should NOT be excluded as they contain compiler settings and other important
# information for Eclipse / Flash Builder.

.DS_Store


================================================
FILE: LICENSE
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Use with the GNU Affero General Public License.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.

  The GNU General Public License does not permit incorporating your program
into proprietary programs.  If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.  But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.


================================================
FILE: README.md
================================================
# Offline-MapMatching
A <a href="https://github.com/qgis/QGIS">QGIS</a>-plugin for matching a trajectory with a network using a Hidden Markov Model and Viterbi algorithm.

## Goal of the project
Matching a trajectory with a network, e.g. a road network, is a classical task in geoinformatics.
It can be differentiated into online, i.e. during the measurement of the trajectory like in car navigation,
and offline, i.e. after the measurement of the trajectory, map matching. This plugin addresses only the offline map mathing.
<br>
Because of inaccuracies of the GNSS signal and/or a low data quality of the network, the positions of the trajectory
and the network are differing. In many cases a simple snapping of the trajectory on the network will not work. Reasons for that
can be e.g. outliers in the trajectory or crossings of edges in the network.
<br>
This plugin provides a statistical approach to solve the problem of offline map matching using the principles of 
Hidden Markov Models (HMM) and the Viterbi algorithm.

## Installation
<img src="screenshots/download_1.png" height="150">
<img src="screenshots/download_2.png" height="250">
<img src="screenshots/download_3.png" height="350">

## Usage
### Match Trajectory
<img src="screenshots/mt_call.png"><br>
- just fill in all entries in the plugin and click the start button
- the tool provides an explanation for each entry directly in the dialog
- the plugin will not run correctly, if at least one entry is not filled
- the CRS of the input layers have to be the same, otherwise the calculations will not start
- information about the current processing step will be written to the log section of the plugin window
- further information, e.g. detailed information about the algorithm, can be found under the next captions
- the gui of the plugin is based on the processing framework, i.e. it will run in the background without freezing QGIS
- the plugin can be started via the processing framework directly in python too
- example for the python command:
```python
processing.run('omm:match_trajectory', {'NETWORK': 'network_layer',
                                        'TRAJECTORY': 'trajectory_layer',
                                        'TRAJECTORY_ID': 'id',
                                        'MAX_SEARCH_DISTANCE': 20.0,
                                        'OUTPUT': 'destination_in_filesystem'})
```
- the processing plugin returns a python dictionary, e.g.:
```python
{'OUTPUT': 'destination_in_filesystem',
'ERROR_CODE': 0,
'COMPUTATION_TIME': 123.45}
```
- the ERROR_CODE will be a negative number if an error occured during processing, otherwise it is equal 0
- the plugin writes all messages, also errors, to the log of the plugin window

### Clip Network
<img src="screenshots/cn_call.png"><br>
- this function can be used to preprocess your network
- the network will be clipped with a buffer around the trajectory (trajectory will be converted to a linestring first)
- the tool provides an explanation for each entry directly in the dialog
- the plugin will not run correctly, if at least one entry is not filled
- the plugin can be started via the toolbox too
- the plugin can be started via the processing framework directly in python too
- example for the python command:
```python
processing.run('omm:clip_network', {'NETWORK': 'network_layer',
                                    'TRAJECTORY': 'trajectory_layer',
                                    'ORDER_FIELD': 'field_name',
                                    'BUFFER_RADIUS': 20.0,
                                    'OUTPUT': 'destination_in_filesystem'})
```

### Reduce Trajectory Density
<img src="screenshots/rd_call.png"><br>
- this function can be used to preprocess your trajectory
- the distance between two consecutive points of the trajectory will be measured
- if the distance is less than a value inserted by the user, the second, i.e. the following, point will be removed
- the parameter KEEP_LAST_FEATURE can be used to keep the last point/feature of the trajectory, regardless of the distance between the previous keeped feature (the parameter is optional, default is <i>False</i>)
- the trajectory layer will not be changed, the reduced trajectory will be stored in a new layer
- example for the python command without optional parameters:
```python
processing.run('omm:reduce_trajectory_density', {'TRAJECTORY': 'trajectory_layer',
                                                 'DISTANCE': 100.0,
                                                 'OUTPUT': ':memory'})
```
- example for the python command with optional parameters:
```python
processing.run('omm:reduce_trajectory_density', {'TRAJECTORY': 'trajectory_layer',
                                                 'DISTANCE': 100.0,
                                                 'KEEP_LAST_FEATURE': True,
                                                 'OUTPUT': ':memory'})
```

## Hints for usage of the map matching
- the progress of the computation will be displayed with a progressbar (starts from zero for every computation step)
and written to the log of the plugin window
- it is recommended to use a low segmented network layer, because candidate points will be searched for
every linestring, i.e. the more linestrings can be found in the maximum search distance, the more candidates will be found
and the more computation time is needed
- use a metric CRS, because distances will be calculated and compared
- the quality of the trajectory is very important for a good result, i.e. big outliers should be removed before running the plugin
(the algorithm does not know, which observation is an outlier, and searches the most likely path for the whole trajectory)
- if positions where measured time-controlled, standing times should be removed before running the plugin

## Hints for developement
- the plugin was created using the QGIS-plugin "plugin builder"
- additional python files related to the Hidden Markov Model and the Viterbi algorithm can be found in the
<a href="https://github.com/jagodki/Offline-MapMatching/tree/master/src/offlinemapmatching/mm">mm</a>-folder
- additional python files related to the processing framework can be found in the 
<a href="https://github.com/jagodki/Offline-MapMatching/tree/master/src/offlinemapmatching/mm_processing">mm_processing</a>-folder

## Description of the computation of the map matching
First, the plugin extracts all intersections from the network, that have a distance to at least one trajectory point less or equal the maximum search distance.
Then, the plugin calculates possible candidate points for each point of the trajectory (observations) (Budig 2012: 10).
Candidates, that are in a range less or equal the maximum search distance of the extracted intersection points, will be ignored. Therefore these extracted intersections will be used as candidates.
The candidates will be arranged in a candidate graph with n layers, n = count of observation points (Budig 2012: 13).
After calculating different posible paths and their probabilities, the path with the highest probability will be found using the Viterbi algorithm.
Below the different parts of the computation in a nutshell, followed by explicit descriptions of each step:
- read the input layers and initialise the internal data structure and objects
- calculate candidate points for each trajectory point using the maximum search distance and store them in a graph
- calculate the probability for each candidate point to be emitted by the correpsonding trajectory point
- calculate the similarity of the transitions between the observations and their candidates
- find the best sequence of candidates using Viterbi algorithm
- create a layer of linestrings using this candidates

### Read the input layers
The network and the trajectory layers will be imported into the plugin and the internal structur of objects will be initialised.
The trajectory cannot be imported, if the layer has no attribute field.
Each position of the trajectory is called observation and one part of the five tupel of a Hidden Markov Model (HMM).

### Calculate candidate points/create candidate graph
A candidate point is a possible position of an observation on the network.
The distance to each linestring of the network will be calculated for every observation.
If the distance is less or equal the maximum search distance, a candidate as the nearest point
on the linestring to the observation will be extracted and added to the candidate graph (Budig 2012: 14):
<img src="screenshots/candidates.png" />
<br>
"A candidate graph is a directed acyclic graph (DAG) which represents all paths that are
considered possible final matching results for the given GPS trajectory" (Budig 2012: 13). The graph will be used as a stochastic matrix
for the HMM. The graph has n layers with n as the count of observations.
Each column contains all candidates of the same/corresponding observation (Budig 2012: 13):
<img src="screenshots/graph.png" />
<br>
The calculation of candidates has a huge impact of the computation time. A higher search distance can result in more candidates per
observation and more possible paths, which will be calculated in the next steps. Also a high segmentation of the network can cause
a lot of candidates per observation with the same problem for the computation time.

### Calculate emission probabilities
A GNSS-measured position is not the real position because of the inaccuracy of the GNSS signal. The GNSS-errors are following
almost, but not strictly, a normal distribution (Newson, Krumm 2009: 4). Also the quality of the network geometry
can be very low. Therefore the candidates are more or less away from the observations.
To calculate the probability, that an observation was emitted by a candidate, a normal distribution is postulated, i.e.
the probability is pending on the distance between observation and candidate, the standard deviation of the GNSS-error and
the expected value of the difference (Newson, Krumm 2009: 4).
The emission probabilities for each candidate are one part of the five tupel of a Hidden Markov Model (Haenelt 2007: 4-5) and the emission probabilities
of the candidates of the first observation will be used as initial state
probabilities in the HMM and represent another part of the five tupel (Raymond et al. 2012: 2244).
The expected value and the standard deviation can be set by the user. The expected value represents the best possible or expected distance between
a candidate and an observer. In most cases, this should be a value of zero (Newson, Krumm 2009: 3).
If the standard deviation of the GNSS-receiver is unknown, it has to be estimated e.g. with this equation (Newson, Krumm 2009: 6):
<br>
<img src="screenshots/estimator_1.png" />
<br>
The term (z - x) means the distance between an observation and its candidate. I reached good results to set the standard deviation to around 2 * (maximum search distance). A larger value represents less trust in the measurement.
A too high or too low standard deviation can overemphasise or underemphasise the emission probability (compared to the transition
probability) and maybe results in an incorrect map matching.

### Calculate transition probabilities
Another and the last tupel of the HMM are the transition probabilities whereat transitions are connections between two candidates in the graph,
i.e. the edges of the graph. Two characteristics of the transition between two candidates and the transition between the two corresponding observations will be compared:
1. the distances
2. the directions
<br>
<br>
The difference between the euklidean distance of the two observations and the distance on the network of two correpsonding candidates can be described by
an exponential probability distribution (Newson, Krumm 2009: 4):
<br>
<img src="screenshots/epd_chart.png" />
<br>
The distance on the network will be calculated using the analysis framework of QGIS and the Dijkstra algorithm with the distance between nodes as
edge weight. The difference of the two distances will be used in the following equation, in which the parameter d is the difference of the distances (Newson, Krumm 2009: 4):
<br>
<img src="screenshots/epd.png" />
<br>
The parameter beta (named "Transition weight" in the GUI of the plugin) can be estimated as around 1.5 * (median of all distances) (Newson, Krumm 2009: 6). I get good results setting beta = 1.5 * (maximum search distance).
A larger value of beta represents more tolerance of non-direct routes and vice-versa.
<br>
<br>
The directions will be calculated using the slope of the line through both observations and the product of the
slops of each subline, i.e. each line between two following vertices, of the shortest way from one candidate
to the other candidate on the network. The angle is the arcustanges of the slope and will have a value between 0 and 180 (the python function atan is used and the
result will be summed up by 90 to avoid negative values). The difference of this two angles will be normalised to receive
a value between 0 and 1 (Budig 2012: 17):
<img src="screenshots/direction.png" />

### Viterbi algorithm
All probabilities (initial, emission and transition) along each path will be multiplied in an efficient way by using the Viterbi algorithm.
The Viterbi algorithm uses results of former calculations again to avoid multiple calculations of same sequences.
Also just the highest probabilities to reach a candidate will be stored and reused (Haenelt 2007: 12).
The most likely path through the HMM, i.e. the path through the candidate graph with the highest probability, is the result of this algorithm.

### Store the path in a new layer
The founded Viterbi path represents the vertices of the resulting line. A routing between following points of the Viterbi path
returns linestrings, which will be written to the output.

### Parameters of plugin-versions less 3.0.0
Starting with version 3.0.0, the former parameters standard deviation and transistion weight will be calculated using the formulas of Newson & Krumm (2009).
This increases the usability of the plugin. The maximum search distance and the quality of the network and trajectory (e.g. density of the trajectory) will now influence the result of the map matching.

## Attribute Table of the resulting layer
#### id
an integer value as counter of the linestrings

#### observation_id_start
the id of the trajectory feature, which corresponds to the start vertex

#### observation_id_end
the id of the trajectory feature, which corresponds to the end vertex

#### emission_probability_start
the emission probability of the start vertex

#### emission_probability_end
the emission probability of the end vertex

#### transition_probability
the transition probability of the current linestring

#### total_probability_start
the total probability of the start vertex of the current linestring, i.e. the product
of the emission and transition probability of the start vertex and the total probability of the parent vertex/candidate

#### total_probability_end
the total probability of the end vertex of the current linestring, i.e. the product of the emission
and transition probability of the end vertex and the total probability of the parent vertex/candidate
(should be the start vertex of the current linestring)

## Screenshots of matched and unmatched trajectories
<table style="width:100%">
    <tr>
        <th>not successful matched</th>
        <th>successful matched</th> 
        <th>successful matched</th>
        <th>legend</th>
    </tr>
    <tr>
        <td><img src="screenshots/not_correct.png" /></td>
        <td><img src="screenshots/correct.png" /></td>
        <td><img src="screenshots/correct_2.png" /></td>
        <td><img src="screenshots/legend.png" /></td>
    </tr>
    <tr>
        <td>st. deviation = 50, max. search dist. = 20, transition weight = 30</td>
        <td>st. deviation = 50, max. search dist. = 20, transition weight = 35</td>
        <td>st. deviation = 60, max. search dist. = 50, transition weight = 30</td>
        <td></td>
    </tr>
</table>

## Further Description
More information about the calculations can be found in the following paper (in german language with an english abstract):<br>
<a href="https://gispoint.de/fileadmin/user_upload/paper_gis_open/AGIT_2019/537669014.pdf">Jung, C. (2019): Offline-MapMatching – A QGIS Plugin for Matching a Trajectory with a Network. In: <i>AGIT ‒ Journal für Angewandte Geoinformatik 5-2019</i> (pp. 156-163). Berlin/Offenbach: Wichmann Verlag. doi: 10.14627/537669014</a>

## Sources
Newson, P., & Krumm, J. (2009). <i>Hidden Markov map matching through noise and sparseness.</i>
In Proceedings of the 17th ACM SIGSPATIAL international conference on advances in geographic information systems (pp. 336-343). ACM. 
Retrieved Oct 10, 2018 from 
<a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.187.5145&rep=rep1&type=pdf">http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.187.5145&rep=rep1&type=pdf</a>
<br><br>
Budig, B. (2012). <i>An algorithm for map matching on incomplete road databases</i>. 
Retrieved Jul 31, 2018 from 
<a href="http://www1.pub.informatik.uni-wuerzburg.de/pub/theses/2012-budig-bachelor.pdf">http://www1.pub.informatik.uni-wuerzburg.de/pub/theses/2012-budig-bachelor.pdf</a>
<br><br>
Raymond, R., Morimura, T., Osogami, T., Hirosue, N. (2012). <i>Map Matching with Hidden Markov Model on Sampled Road Network</i>. 
21st International Conference on Pattern Recognition (ICPR 2012). 
Retrieved Jul 31, 2018 from 
<a href="https://www.computer.org/csdl/proceedings/icpr/2012/2216/00/06460610.pdf">https://www.computer.org/csdl/proceedings/icpr/2012/2216/00/06460610.pdf</a>
<br><br>
Haenelt, K. (2007). <i>Der Viterbi-Algorithmus. Eine Erl\ufffduterung der formalen Spezifikation am Beispiel des Part-of-Speech Tagging</i>. 
Retrieved Jul 31, 2018 from 
<a href="http://kontext.fraunhofer.de/haenelt/kurs/folien/Haenelt_Viterbi-Tutor.pdf">http://kontext.fraunhofer.de/haenelt/kurs/folien/Haenelt_Viterbi-Tutor.pdf</a>
<br>

## Other
The icons are based on products from <a href="https://icons8.com">icons8.com</a>


================================================
FILE: src/offlinemapmatching/__init__.py
================================================
# -*- coding: utf-8 -*-
"""
/***************************************************************************
 OfflineMapMatching
                                 A QGIS plugin
a QGIS-plugin for matching a trajectory with a network using a Hidden Markov Model and Viterbi algorithm
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                             -------------------
        begin                : 2018-06-14
        copyright            : (C) 2018 by Christoph Junh
        email                : jagodki.cj@gmail.com
        git sha              : $Format:%H$
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
 This script initializes the plugin, making it known to QGIS.
"""


# noinspection PyPep8Naming
def classFactory(iface):  # pylint: disable=invalid-name
    """Load OfflineMapMatching class from file OfflineMapMatching.

    :param iface: A QGIS interface instance.
    :type iface: QgsInterface
    """
    #
    from .offline_map_matching import OfflineMapMatching
    return OfflineMapMatching(iface)


================================================
FILE: src/offlinemapmatching/help_docs/help.html
================================================
<html>
    <body>
        <h2><font face="helvetica, arial">Offline-MapMatching</font></h2>
        <p>This plugin matches a trajectory as a point layer with a network as a linestring layer based on a Hidden-Markov-Model and the Viterbi algorithm. There are also routines for preprocessing the data to improve the computation time for the map matching.</p>
        <h4><font face="helvetica, arial">Network-Layer:</font></h4>
        <p><font size=3 face="helvetica, arial">This combobox shows all visible layers of the
        current QGIS project with the geometry type LINESTRING. The network, needed to match the trajectory on it, shall be selected.
        It is recommended, that
        this layer should be a clean graph following a node-edge-model, i.e. different lines
        should only touch other lines, never cross them. You can get souch a clean graph e.g.
        by running the PostGIS function "CreateTopology".</font></p>
        <h4><font face="helvetica, arial">Trajectory-Layer:</font></h4>
        <p><font size=3 face="helvetica, arial">The combobox shows all visible layers of the
        current QGIS project with the geometry type POINT.
        The trajectory, which will be matched with the network, shall be selected.</font></p>
        <h4><font face="helvetica, arial">Trajectory-ID:</font></h4>
        <p><font size=3 face="helvetica, arial">The combobox shows all fields of the chosen trajectory layer.
        The selected field will be added to the result features, so that the vertices of the result layer 
        can be referred to the points of the trajectory.</font></p>
        <h4><font face="helvetica, arial">CRS of output layer (EPSG):</font></h4>
        <p><font size=3 face="helvetica, arial">Insert the EPSG code, which should be used for the result layer.
        It is recommended to insert the CRS of the network and trajectory layer (both layer should have the same CRS).
        It is also recommended to use a metric CRS.</font></p>
        <h4><font face="helvetica, arial">Maximum Search Distance [m]:</font></h4>
        <p><font size=3 face="helvetica, arial">The maximum distance to find points on the network for each trajectory point.
            It is mandatory, that this value is greater oder equal than the highest shortest distance between all trajectory points
            and the whole network. This setting has a big influence on the computing time. Go to the
            <a href="https://github.com/jagodki/Offline-MapMatching">repository on GitHub</a> for further information
            about this setting.</font></p>
        <h4><font face="helvetica, arial">Standard Deviation:</font></h4>
        <p><font size=3 face="helvetica, arial">The standard deviation of the distance between the positions from the
        trajectory and their possible positions on the network. It will be assumed, that the points of the trajectory 
        and their possible positions are normal distributed. A larger value represents less trust in the measurement.
        Go to the <a href="https://github.com/jagodki/Offline-MapMatching">repository on GitHub</a> for
        additonal information about this setting.</font></p>
        <h4><font face="helvetica, arial">Expected Value:</font></h4>
        <p><font size=3 face="helvetica, arial">The expected distance between the positions from the
        trajectory and their possible positions on the network. It is recommended to choose the value 0, i.e.
        ideally, the trajectory points should be on the network, if the accuracy of the coordinates would be 100%.</font></p>
        <h4><font face="helvetica, arial">Transition Weight:</font></h4>
        <p><font size=3 face="helvetica, arial">A weight for the calculation of the difference between the distances of two following trajectory points
        and their corresponding points on the network. A larger value represents more tolerance of non-direct routes.
        Go to the <a href="https://github.com/jagodki/Offline-MapMatching">repository on GitHub</a> for
        additonal information about this setting.</font></p>
        <hr>
        <p><font size=3 face="helvetica, arial"><h4>The plugin runs the following tasks:</h4>
        read the input layers<br><br>
        calculate candidate points for each trajectory point using the maximum search distance and create a graph of candidates<br><br>
        calculate the probability for each candidate point to be emitted by the correpsonding trajectory point<br><br>
        calculate the similarity of the transitions compared to the trajectory points<br><br>
        find the best candidates using Viterbi algorithm<br><br>
        create a layer of linestrings using this candidates</font></p>
        <hr>
        <p><font size=3 face="helvetica, arial">A detailed description about the algorithm is provided
        in the <a href="https://github.com/jagodki/Offline-MapMatching">README document</a> of the project on GitHub.</font></p>
    </body>
</html>

================================================
FILE: src/offlinemapmatching/help_docs/help_processing_clipping_network.html
================================================
<html>
    <body>
        <p><font face="helvetica, arial">This algorithm gives the possibility to clip a network layer by using a trajectory. The trajectory will be transformed to a linestring and buffered after this. Thereafter the buffer will be used as a masking layer for clipping the network.</font></p>
        <h4><font face="helvetica, arial">Network-Layer:</font></h4>
        <p><font size=3 face="helvetica, arial">This combobox shows all layers of the current QGIS project with the geometry type LINESTRING. The features of this layer will be reduced and stored in a memory layer.</font></p>
        <h4><font face="helvetica, arial">Trajectory-Layer:</font></h4>
        <p><font size=3 face="helvetica, arial">This combobox shows all layers of the current QGIS project with the geometry type POINT and will be used for clipping/reducing the network layer.</font></p>
        <h4><font face="helvetica, arial">Order Trajectory by:</font></h4>
        <p><font size=3 face="helvetica, arial">This combobox shows all fields of the trajectory layer. The selected field will be used to order the features of the tractory layer (e.g. order by timestamps or way) and to transform the trajectory into linestrings.</font></p>
        <h4><font face="helvetica, arial">Buffer radius around the Trajectory:</font></h4>
        <p><font size=3 face="helvetica, arial">The radius or distance of the buffer around the trajectory linestring. The resulting polygon will be used to clip the network.</font></p>
        <h4><font face="helvetica, arial">Output:</font></h4>
        <p><font size=3 face="helvetica, arial">The destination where the clipped network should be saved.</font></p>
    </body>
</html>

================================================
FILE: src/offlinemapmatching/help_docs/help_processing_match_trajectory.html
================================================
<html>
    <body>
        <p><font face="helvetica, arial">This algorithm matches a trajectory on a network using a Hidden Markov Model and the Viterbi algorithm.</font></p>
        <h4><font face="helvetica, arial">Network-Layer:</font></h4>
        <p><font size=3 face="helvetica, arial">This combobox shows all layers of the current QGIS project with the geometry type LINESTRING/MULTILINESTRING. The network, needed to match the trajectory on it, shall be selected. It is recommended, that this layer should be a clean graph following a node-edge-model, i.e. different lines should only touch other lines, never cross them.</font></p>
        <h4><font face="helvetica, arial">Trajectory-Layer:</font></h4>
        <p><font size=3 face="helvetica, arial">The combobox shows all layers of the current QGIS project with the geometry type POINT/MULTIPOINT. The trajectory, which will be matched with the network, shall be selected.</font></p>
        <h4><font face="helvetica, arial">Trajectory-ID:</font></h4>
        <p><font size=3 face="helvetica, arial">The combobox shows all fields of the chosen trajectory layer. The selected field will be added to the result features, so that the vertices of the result layer can be referred to the points of the trajectory.</font></p>
        <h4><font face="helvetica, arial">Maximum Search Distance [m]:</font></h4>
        <p><font size=3 face="helvetica, arial">The maximum distance to find points on the network for each trajectory point. It is mandatory, that this value is greater or equal than the highest shortest distance between all trajectory points and the whole network. This setting has a big influence on the computing time. Go to the <a href="https://github.com/jagodki/Offline-MapMatching">repository on GitHub</a> for further information about it.</font></p>
        <h4><font face="helvetica, arial">Matching Type:</font></h4>
        <p><font size=3 face="helvetica, arial">Two matching types are available. The slower type uses a routing between candidate points to calculate the distance of the shortes route between them. The faster type just calculates the euklidean distance of the beeline between the candidate points. The slower type is less vulnerable to outliers of the trajectory. Go to the <a href="https://github.com/jagodki/Offline-MapMatching">repository on GitHub</a> for additonal information about this setting.</font></p>
        <hr>
        <p><font size=3 face="helvetica, arial">A detailed description about the algorithm is provided in the <a href="https://github.com/jagodki/Offline-MapMatching">README document</a> of the project on GitHub.</font></p>
    </body>
</html>

================================================
FILE: src/offlinemapmatching/help_docs/help_processing_reduce_density.html
================================================
<html>
    <body>
        <p><font face="helvetica, arial">This algorithm reduces the density of a given trajectory, i.e. the count of features will be reduced. The original trajectory will not be changed, but the results will be stored in a new layer.</font></p>
        <h4><font face="helvetica, arial">Trajectory Layer:</font></h4>
        <p><font size=3 face="helvetica, arial">This combobox shows all layers of the current QGIS project with the geometry type POINT. A layer with trajectory data should be selected.</font></p>
        <h4><font face="helvetica, arial">Keep the last feature of the trajectory:</font></h4>
        <p><font size=3 face="helvetica, arial">A checkbox to indicate, whether the algorithm keeps the last feature of the trajectory, regardless of the distance to the last feature.</font></p>
        <h4><font face="helvetica, arial">Minimum distance between two consecutive points:</font></h4>
        <p><font size=3 face="helvetica, arial">The minimum distance between consecutive trajectory points. If the distance to the following point is shorter than the inserted value, the following point will be removed.</font></p>
        <h4><font face="helvetica, arial">Output:</font></h4>
        <p><font size=3 face="helvetica, arial">The destination where the reduced trajectory should be saved.</font></p>
    </body>
</html>

================================================
FILE: src/offlinemapmatching/i18n/af.ts
================================================
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="2.0" language="af" sourcelanguage="en">
<context>
    <name>@default</name>
    <message>
        <location filename="test_translations.py" line="48"/>
        <source>Good morning</source>
        <translation>Goeie more</translation>
    </message>
</context>
</TS>


================================================
FILE: src/offlinemapmatching/metadata.txt
================================================
# This file contains metadata for your plugin. Since 
# version 2.0 of QGIS this is the proper way to supply 
# information about a plugin. The old method of 
# embedding metadata in __init__.py will 
# is no longer supported since version 2.0.

# This file should be included when you package your plugin.# Mandatory items:

[general]
name=Offline-MapMatching
qgisMinimumVersion=3.0
description=a QGIS-plugin for matching a trajectory with a network using a Hidden Markov Model and Viterbi algorithm 
version=3.0.0
author=Christoph Jung
email=jagodki.cj@gmail.com

about=Because of inaccuracies of the GNSS signal and/or a low data quality of the network, the positions of the trajectory and the network are differing. In many cases a simple snapping of the trajectory on the network will not work to reproduce the real track on the network. Reasons for that can be e.g. outliers in the trajectory or crossings of edges in the network. This plugin provides a statistical approach to solve the problem of offline map matching using the principles of Hidden Markov Models (HMM) and the Viterbi algorithm.

tracker=https://github.com/jagodki/Offline-MapMatching/issues
repository=https://github.com/jagodki/Offline-MapMatching
# End of mandatory metadata

# Recommended items:

# Uncomment the following line and add your changelog:
changelog=
    3.0.0 - new algorithm to improve calculation time
    	2.5.2 - show icon in plugin manager correctly
	2.5.1 - make the new parameter from v2.5.0 optional to keep the plugin backward compatible
	2.5.0 - the algorithm "reduce trajectory density" has a new parameter for keeping the last vertex/feature after processing
	2.4.0 - add a new preprocessing algorithm (reduce trajectory density)
	2.3.2 - avoid a rare error with the crs selector
	2.3.1 - remove an error while unloading the plugin
	2.3.0 - provide a processing routine to reduce the network
	2.2.2 - remove of an error in the calculation of the transition probabilities
	2.2.1 - remove of an error in the implementation of the Viterbi-algorithm, that could cause an empty result layer
	2.2.0 - log the id of the trajectory point for which the search distance is too low
	2.1.1 - allow the processing of trajectory positions with the same coordinates
	2.1.0 - reuse routing calculations for improvement of computation time
	2.0.0 - introduce a new parameter to avoid u-turns
	1.1.3 - experimental version to avoid u-turns (preparation for the new major version)
	1.1.2 - the plugin will not crash anymore, if a raster layer is loaded
	1.1.1 - bugs according to the case of different directions of the network and the trajectory have been solved
	1.1.0 - make the plugin accessible via the processing framework
	1.0.3 - enable the usage of layers with geometries containing Z- and/or M-value
	1.0.2 - edit metadata and improve exception handling
	1.0.1 - adjust field names of result layer and some minor changes
	1.0.0 - first release

# Tags are comma separated with spaces allowed
tags=trajectory, map matching, viterbi, hmm

homepage=https://github.com/jagodki/Offline-MapMatching
category=Vector
icon=icons/icon.png
# experimental flag
experimental=False

# deprecated flag (applies to the whole plugin, not just a single version)
deprecated=False

================================================
FILE: src/offlinemapmatching/mm/__init__.py
================================================


================================================
FILE: src/offlinemapmatching/mm/helper/__init__.py
================================================


================================================
FILE: src/offlinemapmatching/mm/helper/measurement_statistics.py
================================================
import statistics

class MeasurementStatistics:
    
    def __init__(self):
        self.measurments = []
    
    def addMeasurement(self, value):
        self.measurments.append(value)
    
    def getMeanValue(self):
        return statistics.mean(self.measurments)
    
    def getStandardDeviation(self):
        return statistics.stdev(self.measurments)
    
    

================================================
FILE: src/offlinemapmatching/mm/hidden_states/__init__.py
================================================


================================================
FILE: src/offlinemapmatching/mm/hidden_states/candidate.py
================================================
import math
from ..observation.observation import *

class Candidate:
    
    def __init__(self, point, distance, observation_id):
        self.point = point
        self.distance_to_observation = distance
        self.observation_id = observation_id
    
    def getEmissionProbability(self, sigma, my):
        return (1 / math.sqrt(2 * math.pi * sigma)) * math.pow(math.e, -1 * math.pow(self.distance_to_observation - my, 2) / (2 * math.pow(sigma, 2)))
    


================================================
FILE: src/offlinemapmatching/mm/hidden_states/hidden_model.py
================================================
from ..observation.network import *
from ..observation.trajectory import *
from ..observation.observation import *
from .candidate import *
from .transition import *
from ..helper.measurement_statistics import *
from qgis.core import *
import os, math
from PyQt5.QtWidgets import QProgressBar, QApplication
from PyQt5.QtCore import QVariant, QDir

class HiddenModel:
    
    def __init__(self, trajectory, network):
        self.trajectory = trajectory
        self.network = network
        self.counter_candidates = 0
        self.candidate_graph = []
        self.candidates = {}
        self.candidates_backtracking = {}
        self.observation_measurements = MeasurementStatistics()
        self.transition_measurements = MeasurementStatistics()
        self.pb = None
        self.feedback = None
        self.feedback_total = 100.0
    
    def createGraph(self, maximum_distance):
        #init progressbar
        self.initFeedback(len(self.trajectory.observations))
        
        #init data structur
        self.candidate_graph = []
        self.candidates = {}
        self.counter_candidates = 0
        
        #iterate over all observations from our trajectory
        for observation in self.trajectory.observations:
            
            #extract all candidates of the current observation
            candidates = observation.getCandidates(self.network, maximum_distance)
            #candidates = observation.getAllCandidates(self.network, maximum_distance)
            if len(candidates) == 0:
                QgsMessageLog.logMessage('could not find any candidates for trajectory point ' + str(observation.id), level=Qgis.Info)
                return -5
            else:
                #create the current level of the graph and the candidate measurements
                current_graph_level = {}
                for candidate in candidates:
                    
                    #store the observation measurements for a later usage of their statistics
                    #e.g. for calculations of the emission probabilities
                    self.observation_measurements.addMeasurement(candidate.distance_to_observation)
                    
                    #candidate.calculateEmissionProbability(observation, sigma, my)
#                    current_graph_level[self.counter_candidates] = {
#                                                  #'observation_id' : observation.id,
#                                                  #'emitted_probability' : candidate.emission_probability,
#                                                  'candidate': candidate,
#                                                  'transition_probabilities' : {},
#                                                  'transition_probability' : 0.0,
#                                                  'curvature_probability' : 0.0,
#                                                  'total_probability' : 0.0
#                                                  }
                    current_graph_level.update({self.counter_candidates: {
                                                  #'observation_id' : observation.id,
                                                  #'emitted_probability' : candidate.emission_probability,
                                                  'candidate': candidate,
                                                  'transition_probabilities' : {},
                                                  'transition_probability' : 0.0,
                                                  'curvature_probability' : 0.0,
                                                  'total_probability' : 0.0
                                                  }})
                    
                    #store the candidate in a dictionary (key can be used to find the candidate in the graph and vice versa)
                    self.counter_candidates += 1
                
                #add the current graph level to the graph
                self.candidate_graph.append(current_graph_level)
            
            #update progressbar
            self.updateFeedback()
        
        return 0
    
    def createBacktracking(self):
        #init progressbar
        self.initFeedback(len(self.candidate_graph))
        
        self.candidates_backtracking = {}
        
        for i, graph_level in enumerate(self.candidate_graph):
            
            #the candidates of the first observation have no parent
            if i != 0:
                for id, entry in graph_level.items():
                    
                    #get all transition probabilities of the current entry and iterate over them to find the highest total probability
                    transition_probabilities = entry['transition_probabilities']
                    for previous_id, transition in transition_probabilities.items():
                        
                        #calculate the probabilities of the current transition
                        start_observation = self.trajectory.observations[i - 1]
                        end_observation = self.trajectory.observations[i]
                        distance_between_observations = start_observation.point.distance(end_observation.point)
                        transition.setDirectionProbability(start_observation, end_observation)
                        transition.setRoutingProbability(distance_between_observations, self.transition_measurements.getStandardDeviation())
                        transition.setTransitionProbability()
                        
                        #calculate the total probability and compare it
                        current_total_probability = transition.transition_probability * entry['candidate'].getEmissionProbability(self.observation_measurements.getStandardDeviation(), 0.0)# * self.candidate_graph[i - 1][key]['total_probability']
                        
#                        #calculate the emission probability for the current entry/candidate in the graph level
#                        current_total_probability = value * entry["candidate"].getEmissionProbability(self.observation_measurements.getStandardDeviation(), 0.0) * self.candidate_graph[i - 1][key]['total_probability']
                        
                        if current_total_probability >= entry['total_probability']:
                            entry['total_probability'] = current_total_probability
                            entry['transition_probability'] = transition.transition_probability
                            self.candidates_backtracking[id] = previous_id
            
            #update progressbar
            self.updateFeedback()
        
        return 0

    def findViterbiPath(self):
        #init an array to store all candidates of the most likely path
        viterbi_path = []
        
        #find the highest total probability in the last graph level
        highest_prob = 0.0
        id = None
        graph_counter = len(self.candidate_graph) - 1
        last_graph_level = self.candidate_graph[graph_counter]
        
        for candidate_id, entry in last_graph_level.items():
            if entry['total_probability'] >= highest_prob:
                highest_prob = entry['total_probability']
                id = candidate_id
        
        #add the last vertex of the path
        last_graph_level_entry = self.candidate_graph[graph_counter][id]
        viterbi_path.insert(0, {'vertex': last_graph_level_entry["candidate"],
                                'total_probability': highest_prob,
                            'emitted_probability': last_graph_level_entry["candidate"].getEmissionProbability(self.observation_measurements.getStandardDeviation(), 0.0),
                            'transition_probability': last_graph_level_entry['transition_probability'],
                            'observation_id': last_graph_level_entry["candidate"].observation_id
                            })
        
        #now find all parents of this vertex/candidate
        graph_counter -= 1
        current_id = self.candidates_backtracking[id]
        while(current_id is not None and graph_counter >= 0):
            searched_graph_level_entry = self.candidate_graph[graph_counter][current_id]
            viterbi_path.insert(0, {'vertex': searched_graph_level_entry["candidate"],
                                    'total_probability': searched_graph_level_entry['total_probability'],
                                    'emitted_probability': searched_graph_level_entry["candidate"].getEmissionProbability(self.observation_measurements.getStandardDeviation(), 0.0),
                                    'transition_probability': searched_graph_level_entry['transition_probability'],
                                    'observation_id': searched_graph_level_entry["candidate"].observation_id
                                    })
            #preparation for the next iteration
            if current_id in self.candidates_backtracking:
                current_id = self.candidates_backtracking[current_id]
            
            graph_counter -= 1
        
        return viterbi_path
    
    def setTransitions(self, fast_map_matching=False):
        #init progressbar
        self.initFeedback(len(self.trajectory.observations))
        
        for i, observation in enumerate(self.trajectory.observations):
            
            #skip the first observation, because first observation has no parent
            if i != 0:
                
                #get the current and previous graph level
                previous_graph_level = self.candidate_graph[i - 1]
                current_graph_level = self.candidate_graph[i]
                
                for previous_id, previous_entry in previous_graph_level.items():
                    
                    for current_id, current_entry in current_graph_level.items():
                        
                        #get the candidates
                        current_candidate = current_entry["candidate"]
                        previous_candidate = previous_entry["candidate"]
                        
                        #create a new transition
                        transition = None
                        if fast_map_matching is True:
                            transition = Transition(previous_candidate, current_candidate, self.network, False, True)
                        else:
                            transition = Transition(previous_candidate, current_candidate, self.network, self.candidatesHaveDifferentPositions(current_candidate, previous_candidate))
                        
                        #calculate the probabilities of the transition
#                        transition.setDirectionProbability(self.trajectory.observations[i - 1], observation)
#                        transition.setRoutingProbability(observation.point.distance(self.trajectory.observations[i - 1].point), beta)
#                        transition.setTransitionProbability()
                        
                        #calculate the difference of the distances between candidates and observations
                        transition_length = transition.getLengthOfTransition()
                        observation_distance = math.sqrt(math.pow(observation.point.asPoint().x() - self.trajectory.observations[i - 1].point.asPoint().x(), 2) + math.pow(observation.point.asPoint().y() - self.trajectory.observations[i - 1].point.asPoint().y(), 2))
                        self.transition_measurements.addMeasurement(abs(transition_length - observation_distance))
                        
                        #insert the transition into the graph
#                        current_entry['transition_probabilities'] = {previous_id : transition.transition_probability}
                        current_entry['transition_probabilities'].update({previous_id : transition})

            self.updateFeedback()
            
        return 0
    
    def candidatesHaveDifferentPositions(self, candidate_1, candidate_2):
        #get coordinates of the previous entry and the current candidate
        x_candidate_1 = candidate_1.point.asPoint().x()
        y_candidate_1 = candidate_1.point.asPoint().y()
        x_candidate_2 = candidate_2.point.asPoint().x()
        y_candidate_2 = candidate_2.point.asPoint().y()
                        
        #if points are not equal, return True, otherwise False
        if x_candidate_1 != x_candidate_2 or y_candidate_1 != y_candidate_2:
            return True
        else:
            return False

    def setStartingProbabilities(self):
        first_graph_level = self.candidate_graph[0]
        
        #init progressbar
        self.initFeedback(len(first_graph_level))
        
        for id, entry in first_graph_level.items():
            entry['total_probability'] = entry['candidate'].getEmissionProbability(self.observation_measurements.getStandardDeviation(), 0.0)
            self.candidates_backtracking[id] = None
            
            self.updateFeedback()
        
        return 0
    
    def addFeaturesToLayer(self, features, attributes, crs):
        #create a new layer
        layer = QgsVectorLayer('LineString?crs=' + crs + '&index=yes', 'matched trajectory', 'memory')
        
        #load the layer style
        dir = os.path.dirname(__file__)
        filename = os.path.abspath(os.path.join(dir, '..', '..', 'style.qml'))
        layer.loadNamedStyle(filename, loadFromLocalDb=False)
        
        #add the layer to the project
        layer.startEditing()
        layer_data = layer.dataProvider()
        layer_data.addAttributes(attributes)
        layer.updateFields()
        
        #add features to the layer
        layer.addFeatures(features)
        layer.commitChanges()
    
        #add the layer to the map
        QgsProject.instance().addMapLayer(layer)
        
        return layer
    
    def getPathOnNetwork(self, vertices, field_array):
        #init progressbar
        self.initFeedback(len(vertices))
        
        #create an array to store all features
        features = []
        
        #iterate over the vertices
        for i, vertex in enumerate(vertices):
            
            #if we are in the first loop, we skip them because we have no previous point to create a routing with start and end
            if i != 0:
                
                #check, whether the two current candidates share the same position or not
                if self.candidatesHaveDifferentPositions(vertices[i - 1]['vertex'], vertex['vertex']) == False:
                    self.updateFeedback()
                    continue
                
                #get all edges of the graph/network along the shortest way from the previous to the current vertex
                points = self.network.routing(vertices[i - 1]['vertex'].point.asPoint(), vertex['vertex'].point.asPoint())
                
                if points == -1:
                    return points
                
                #now create a new line feature and add all needed fields
                fields = QgsFields()
                for field in field_array:
                    fields.append(field)
                feature = QgsFeature(fields)
                
                #create the geometry of the new feature
                feature.setGeometry(QgsGeometry.fromPolylineXY(points))
                
                #insert the attributes and add the feature to the layer
                feature.setAttribute('id', i)
                feature.setAttribute('total_probability_start', vertices[i - 1]['total_probability'])
                feature.setAttribute('total_probability_end', vertex['total_probability'])
                feature.setAttribute('emission_probability_start', vertices[i - 1]['emitted_probability'])
                feature.setAttribute('emission_probability_end', vertex['emitted_probability'])
                feature.setAttribute('transition_probability', vertices[i - 1]['transition_probability'])
                feature.setAttribute('observation_id_start', vertices[i - 1]['observation_id'])
                feature.setAttribute('observation_id_end', vertex['observation_id'])
                features.append(feature)
            
            self.updateFeedback()
        
        return features
    
    def initProgressbar(self, maximum):
        if self.pb is not None:
            self.pb.setValue(0)
            self.pb.setMaximum(maximum)
            QApplication.processEvents()
    
    def updateProgressbar(self):
        if self.pb is not None:
            self.pb.setValue(self.pb.value() + 1)
            QApplication.processEvents()
    

    def initFeedback(self, maximum):
        if self.feedback is not None:
            print(self.feedback.progress())
            self.feedback.setProgress(0)
            self.feedback_total = 100.0 / maximum
        else:
            print("feedback is None")
    
    def updateFeedback(self):
        if self.feedback is not None:
            self.feedback.setProgress(int(self.feedback_total + self.feedback.progress()) + 1)


================================================
FILE: src/offlinemapmatching/mm/hidden_states/transition.py
================================================
from .candidate import *
import math

class Transition:
    
    def __init__(self, start_candidate, end_candidate, network, different_positions, use_beeline=False):
        self.start_candidate = start_candidate
        self.end_candidate = end_candidate
        self.network = network
        self.direction_probability = 0.0
        self.routing_probability = 0.0
        self.transition_probability = 0.0
        self.points_on_network = self.getAllpoints_on_network(network) if different_positions else 0
        self.use_beeline = use_beeline
    
    def setDirectionProbability(self, start_observation, end_observation):
        #variables to store the slops
        m_observation = 0.0
        m_candidate = 0.0
        
        #variable to store the intermediate results of the probability
        p_intermediate = 1.0
        
        #calculate the slope of the observations using arctan (we get results between 0 and 180 degrees)
        if (end_observation.point.asPoint().x() - start_observation.point.asPoint().x()) != 0:
            m_observation = math.degrees(math.atan((end_observation.point.asPoint().y() -
                                                    start_observation.point.asPoint().y()) /
                                                   (end_observation.point.asPoint().x() -
                                                    start_observation.point.asPoint().x()))) + 90
        elif end_observation.point.asPoint().y() != start_observation.point.asPoint().y():
            m_observation = 90.0
        
        #iterate over all points and use their precursor to calculate directions
        if self.points_on_network != -1 and self.points_on_network != 0:
            for i, current_point in enumerate(self.points_on_network):
                if i != 0:
                    
                    #get the precursor of the current point
                    precursor = self.points_on_network[i - 1]
                    
                    #calculate the slope of the points using arctan (we get results between 0 and 180 degrees)
                    if (current_point.x() - precursor.x()) != 0:
                        m_candidate = math.degrees(math.atan((current_point.y() - precursor.y()) /
                                                             (current_point.x() - precursor.x()))) + 90
                    elif current_point.y() - precursor.y():
                        m_candidate = 90.0
                    
                    #calculate the difference of the slopes
                    difference = abs(m_observation - m_candidate)
                    
                    #normalisation of the difference
                    p_intermediate *= (180 - difference) / 180
                    
                    #clear the slope
                    m_candidate = 0.0
        
        #store the result
        if self.points_on_network != -1:
            self.direction_probability = p_intermediate
    
    def setRoutingProbability(self, distance_between_observations, beta):
        #get the distance of the shortest path between the two candidates of the current transition
        distance_on_network = 0
        if self.use_beeline is True:
            distance_on_network = self.getDistanceOfBeeline()
        else:
            distance_on_network = self.getLengthOfTransition()
        
        #calculate the difference between the distances of the observations and the candidates
        difference = abs(distance_on_network - distance_between_observations)

        #calculate the exponential probability distribution
        if distance_on_network != -1:
            self.routing_probability = 1 / beta * math.pow(math.e, -1 * difference / beta)
    
    def setTransitionProbability(self):
        self.transition_probability = self.direction_probability * self.routing_probability
        #if self.direction_probability != None and self.routing_probability != None:
            #self.transition_probability = self.direction_probability * self.routing_probability
            #return True
        #else:
            #return False
    
    def getAllpoints_on_network(self, network):
        #get all points of the shortest path on the network from the start to the end of the transistion and store them
        return network.routing(self.start_candidate.point.asPoint(), self.end_candidate.point.asPoint())
    
    def getLengthOfTransition(self):
        #points == -1, if routing was not possible; points == 0, if canidates share the same position
        if self.points_on_network == -1 or self.points_on_network == 0:
            return self.points_on_network
        else:
            distance = 0
            for i, vertex in enumerate(self.points_on_network):
                
                #get the distance between the current vertice and the next vertice
                if len(self.points_on_network) > (i + 1):
                    distance = distance + vertex.distance(self.points_on_network[i + 1].x(), self.points_on_network[i + 1].y())
                else:
                    return distance
            return distance
    
    def getDistanceOfBeeline(self):
        return self.start_candidate.point.distance(self.end_candidate.point)
    

================================================
FILE: src/offlinemapmatching/mm/map_matcher.py
================================================
from PyQt5.QtWidgets import QProgressBar, QComboBox, QLabel, QApplication
from qgis.core import *
from .hidden_states.hidden_model import *
from .observation.network import *
from .observation.trajectory import *

class MapMatcher:
    
    def __init__(self):
        self.layers = []
        self.attributes = []
        self.hidden_model = None
        self.network = None
        self.trajectory = None
    
    def startViterbiMatchingGui(self, pb, trajectory_name, network_name, attribute_name, sigma, my, beta, max_dist, label, crs):
        check_results = 0
        
        label.setText('initialise data structur...')
        QgsMessageLog.logMessage('initialise data structur...', level=Qgis.Info)
        self.setUp(network_name, trajectory_name, attribute_name, pb)
        
        label.setText('create candidate graph...')
        QgsMessageLog.logMessage('create candidate graph...', level=Qgis.Info)
        #check_results = self.hidden_model.createGraph(sigma, my, max_dist)
        check_results = self.hidden_model.createGraph(max_dist)
        if check_results != 0:
            label.setText('error during calculation of candidates...')
            QgsMessageLog.logMessage('the maximum search distance is too low for one trajectory point to find at least one candidate', level=Qgis.Info)
            return -1
        
        label.setText('calculate starting probabilities...')
        QgsMessageLog.logMessage('calculate starting probabilities...', level=Qgis.Info)
        check_results = self.hidden_model.setStartingProbabilities()
        if check_results != 0:
            label.setText('error during calculation of starting probabilities...')
            QgsMessageLog.logMessage('calculation of starting probabilities was not successfull, maybe an exception was thrown', level=Qgis.Info)
            return -2
        
        
        label.setText('calculate transition probabilities...')
        QgsMessageLog.logMessage('calculate transition probabilities...', level=Qgis.Info)
        #check_results = self.hidden_model.setTransitionProbabilities(beta)
        check_results = self.hidden_model.setTransitions()
        if check_results != 0:
            label.setText('error during calculation of transition probabilities...')
            QgsMessageLog.logMessage('calculation of transition probabilities was not succesfull, have a look on the used parameters and the check the datasets', level=Qgis.Info)
            return -3
        
        
        label.setText('create backtracking...')
        QgsMessageLog.logMessage('create backtracking...', level=Qgis.Info)
        check_results = self.hidden_model.createBacktracking()
        if check_results != 0:
            label.setText('error during calculation of backtracking...')
            QgsMessageLog.logMessage('it was not able to create a complete backtracking, maybe the calculated probabilities are corrupt', level=Qgis.Info)
            return -4
        
        
        label.setText('get most likely path...')
        QgsMessageLog.logMessage('get most likely path...', level=Qgis.Info)
        vertices = self.hidden_model.findViterbiPath()
        if len(vertices) <= 1:
            label.setText('error during calculating the most likely path...')
            QgsMessageLog.logMessage('cannot calculate the most likely path, maybe backtracking went wrong, check your parameters', level=Qgis.Critical)
            return -5
        
        label.setText('get network path...')
        QgsMessageLog.logMessage('get network path...', level=Qgis.Info)
        features = self.hidden_model.getPathOnNetwork(vertices, self.defineAttributes())
        if features == -1:
            label.setText('error during calculating the path on network...')
            QgsMessageLog.logMessage('routing between the points of the most likely path does not work', level=Qgis.Critical)
            return -6
        
        layer = self.hidden_model.addFeaturesToLayer(features, self.defineAttributes(), crs)
        layer.select([])
        QgsProject.instance().addMapLayer(layer)
        
        label.setText('finished ^o^')
        QgsMessageLog.logMessage('finished ^o^', level=Qgis.Info)
        return 0
    
    def startViterbiMatchingProcessing(self, trajectory_name, network_name, attribute_name, max_dist, feature_sink, feedback, fast_map_matching=False):
        check_results = 0
        #total = 100.0 / 8
        #current = 1
        
        QgsMessageLog.logMessage('initialise data structur...', level=Qgis.Info)
        feedback.pushInfo('initialise data structur...')
        self.setUp(network_name, trajectory_name, attribute_name, feedback)
        #feedback.setProgress(int(current * total))
        #current += 1
        if feedback.isCanceled():
                return -99
        
        QgsMessageLog.logMessage('create candidate graph...', level=Qgis.Info)
        feedback.pushInfo('create candidate graph...')
        #check_results = self.hidden_model.createGraph(sigma, my, max_dist)
        check_results = self.hidden_model.createGraph(max_dist)
        #feedback.setProgress(int(current * total))
        #current += 1
        if feedback.isCanceled():
                return -99
        if check_results != 0:
            error_text = 'the maximum search distance is too low for one trajectory point point to find at least one candidate'
            feedback.pushInfo(error_text)
            QgsMessageLog.logMessage(error_text, level=Qgis.Info)
            return -1
        
        QgsMessageLog.logMessage('calculate starting probabilities...', level=Qgis.Info)
        feedback.pushInfo('calculate starting probabilities...')
        check_results = self.hidden_model.setStartingProbabilities()
        #feedback.setProgress(int(current * total))
        #current += 1
        if feedback.isCanceled():
                return -99
        if check_results != 0:
            error_text = 'calculation of starting probabilities was not successfull, maybe an exception was thrown'
            feedback.pushInfo(error_text)
            QgsMessageLog.logMessage(error_text, level=Qgis.Info)
            return -2
        
        
        QgsMessageLog.logMessage('calculate transition probabilities...', level=Qgis.Info)
        feedback.pushInfo('calculate transition probabilities...')
        #check_results = self.hidden_model.setTransitionProbabilities(beta)
        check_results = self.hidden_model.setTransitions(fast_map_matching)
        #feedback.setProgress(int(current * total))
        #current += 1
        if feedback.isCanceled():
                return -99
        if check_results != 0:
            error_text = 'calculation of transition probabilities was not succesfull, have a look on the used parameters and the check the datasets'
            feedback.pushInfo(error_text)
            QgsMessageLog.logMessage(error_text, level=Qgis.Info)
            return -3
        
        QgsMessageLog.logMessage('create backtracking...', level=Qgis.Info)
        feedback.pushInfo('create backtracking...')
        check_results = self.hidden_model.createBacktracking()
        #feedback.setProgress(int(current * total))
        #current += 1
        if feedback.isCanceled():
                return -99
        if check_results != 0:
            error_text = 'it was not able to create a complete backtracking, maybe the calculated probabilities are corrupt'
            feedback.pushInfo(error_text)
            QgsMessageLog.logMessage(error_text, level=Qgis.Info)
            return -4
        
        QgsMessageLog.logMessage('get most likely path...', level=Qgis.Info)
        feedback.pushInfo('get most likely path...')
        vertices = self.hidden_model.findViterbiPath()
        #feedback.setProgress(int(current * total))
        #current += 1
        if feedback.isCanceled():
                return -99
        if len(vertices) == 0:
            error_text = 'cannot calculate the most likely path, maybe backtracking went wrong, check your parameters'
            feedback.pushInfo(error_text)
            QgsMessageLog.logMessage(error_text, level=Qgis.Critical)
            return -5
        
        QgsMessageLog.logMessage('get network path...', level=Qgis.Info)
        feedback.pushInfo('get network path...')
        features = self.hidden_model.getPathOnNetwork(vertices, self.defineAttributes())
        #feedback.setProgress(int(current * total))
        #current += 1
        if feedback.isCanceled():
                return -99
        if features == -1:
            error_text = 'routing between the points of the most likely path does not work'
            feedback.pushInfo(error_text)
            QgsMessageLog.logMessage(error_text, level=Qgis.Critical)
            return -6
        
        feature_sink.addFeatures(features)
        #feedback.setProgress(int(current * total))
        #current += 1
        
        return 0
    
    def fillLayerComboBox(self, iface, combobox, geom_type):
        #first clear the combobox
        combobox.clear()
        
        #get all layers in the current QGIS project
        self.layers = []
        self.layers = iface.mapCanvas().layers()
        
        #populate the combobox
        for layer in self.layers:
            #ignore raster layer, because just vector layers have a wkbType
            if layer.type() == 0:
                if (QgsWkbTypes.flatType(layer.wkbType()) == QgsWkbTypes.Point and geom_type == 'POINT') or (QgsWkbTypes.flatType(layer.wkbType()) == QgsWkbTypes.LineString and geom_type == 'LINESTRING'):
                    combobox.addItem(layer.name())
    
    def fillAttributeComboBox(self, combobox, layername):
        #first clear the combobox
        combobox.clear()
        
        #extract all attributes
        layer = self.getLayer(layername)
        if layer is not None:
            self.attributes = layer.fields()
        
        #populate the combobox
        for attr in self.attributes:
            combobox.addItem(attr.name())
    
    def getLayer(self, layername):
        for layer in self.layers:
            if layer.name() == layername:
                return layer
        return None
    
    def setUp(self, line_layer, point_layer, point_attr, feedback):
        if type(line_layer) is str:
            self.network = Network(self.getLayer(line_layer))
        else:
            self.network = Network(line_layer)
        
        
        if type(line_layer) is str:
            self.trajectory = Trajectory(self.getLayer(point_layer), point_attr)
        else:
            self.trajectory = Trajectory(point_layer, point_attr)
        
        
        self.hidden_model = HiddenModel(self.trajectory, self.network)
        self.hidden_model.feedback = feedback
    
    def defineAttributes(self):
        attributes = [QgsField('id', QVariant.Int),
                      QgsField('observation_id_start', QVariant.Int),
                      QgsField('observation_id_end', QVariant.Int),
                      QgsField('emission_probability_start', QVariant.Double),
                      QgsField('emission_probability_end', QVariant.Double),
                      QgsField('transition_probability', QVariant.Double),
                      QgsField('total_probability_start', QVariant.Double),
                      QgsField('total_probability_end', QVariant.Double)]
        return attributes
    


================================================
FILE: src/offlinemapmatching/mm/observation/__init__.py
================================================


================================================
FILE: src/offlinemapmatching/mm/observation/intersection.py
================================================
from qgis.core import *

class Intersection:
    
    def __init__(self, geometry, edge_ids):
        self.geometry = geometry
        self.edge_ids = edge_ids
    
    



================================================
FILE: src/offlinemapmatching/mm/observation/network.py
================================================
from qgis.analysis import *
from qgis.core import *
from .intersection import *
import processing

class Network:
    
#    def __init__(self, linestring_layer, precalculated_network):
    def __init__(self, linestring_layer):
        self.vector_layer = linestring_layer
        self.intersections = []
        self.extractAllIntersections()
        #self.precalculated_network = precalculated_network
    
    def extractAllIntersections(self):
        #dissolve the network
        result_dissolve = processing.run("native:dissolve", {
            'INPUT': self.vector_layer,
            'OUTPUT': 'memory:dissolve'
        })
        
        #create single geometries from the dissolved network
        result_single = processing.run("native:multiparttosingleparts", {
            'INPUT': result_dissolve["OUTPUT"],
            'OUTPUT': 'memory:single'
        })
        
        #get the line intersections
        result_intersections = processing.run("native:lineintersections", {
            'INPUT': result_single["OUTPUT"],
            'INTERSECT': result_single["OUTPUT"],
            'OUTPUT': 'memory:intersections'
        })
        
        #remove duplicate geometries
        result_cleaned = processing.run("qgis:deleteduplicategeometries", {
            'INPUT': result_intersections["OUTPUT"],
            'OUTPUT': 'memory:cleaned'
        })
        
        #create intersection objects
        for feature in result_cleaned["OUTPUT"].getFeatures():
            intersecting_edges = []
            
            #iterate over alle edges of the network
            for edge in self.vector_layer.getFeatures():
                if edge.geometry().intersects(feature.geometry()):
                    intersecting_edges.append(edge.id())
            
            self.intersections.append(Intersection(feature.geometry(), intersecting_edges))
    
    def routing(self, start, end):
        #create director and strategy
        director = QgsVectorLayerDirector(self.vector_layer, -1, '', '', '', 2)
        strategy = QgsNetworkDistanceStrategy()
        director.addStrategy(strategy)
        
        #buildiung the graph
        builder = QgsGraphBuilder(self.vector_layer.sourceCrs())
        tied_points = director.makeGraph(builder, [start, end])
        graph = builder.graph()
        start_id = graph.findVertex(tied_points[0])
        end_id = graph.findVertex(tied_points[1])
        
        #start routing
        (tree, cost) = QgsGraphAnalyzer.dijkstra(graph, start_id, 0)
        if tree[end_id] == -1:
            return -1
        else:
            points = []
            cur_pos = end_id
            
            #get the first vertex
            if cur_pos != start_id:
                if cur_pos == graph.edge(tree[cur_pos]).toVertex():
                    #insert the vertices to the result list
                    points.insert(0, graph.vertex(graph.edge(tree[cur_pos]).toVertex()).point())
                    points.insert(0, graph.vertex(graph.edge(tree[cur_pos]).fromVertex()).point())
                    
                    #set cur_pos to the next vertex
                    cur_pos = graph.edge(tree[cur_pos]).fromVertex()
                else:
                    #insert the vertices to the result list
                    points.insert(0, graph.vertex(graph.edge(tree[cur_pos]).fromVertex()).point())
                    points.insert(0, graph.vertex(graph.edge(tree[cur_pos]).toVertex()).point())
                    
                    #set cur_pos to the next vertex
                    cur_pos = graph.edge(tree[cur_pos]).toVertex()
                    
            
            while cur_pos != start_id:
                #just insert the vertex of the current edge, which does not already exist in the result list
                if cur_pos == graph.edge(tree[cur_pos]).toVertex():
                    points.insert(0, graph.vertex(graph.edge(tree[cur_pos]).fromVertex()).point())
                    
                    #set cur_pos to the next vertex
                    cur_pos = graph.edge(tree[cur_pos]).fromVertex()
                else:
                    points.insert(0, graph.vertex(graph.edge(tree[cur_pos]).toVertex()).point())
                    
                    #set cur_pos to the next vertex
                    cur_pos = graph.edge(tree[cur_pos]).toVertex()
                
            return points
    
    



================================================
FILE: src/offlinemapmatching/mm/observation/observation.py
================================================
from .network import *
from .intersection import *
from ..hidden_states.candidate import *
from qgis.core import *

class Observation:
    
    def __init__(self, point, id):
        self.point = point
        self.id = id
    
    def getAllCandidates(self, network, max_distance):
        candidates = []
        
        #iterate over all lines of the network to check for candidates on this lines
        for feature in network.vector_layer.getFeatures():
            #init some vars
            linestring = feature.geometry()
            distance = self.point.distance(linestring)
            
            #check whether the distance is equal or less the search distance
            if distance <= max_distance:
                candidates.append(Candidate(linestring.nearestPoint(self.point), distance, self.id))
        
        return candidates
    
    def getCandidates(self, network, max_distance):
        candidates = []
        intersections_within_distance = []
        
        #get all intersection points within the search distance
        for intersection in network.intersections:
            if intersection.geometry.distance(self.point) <= max_distance:
                intersections_within_distance.append(intersection)
        
        # #iterate over all lines of the network to check for candidates on this lines
        # for feature in network.vector_layer.getFeatures():
            
            # #check if the current edge intersects an intersection within search distance
            # skip_iteration_step = False
            # for intersection in intersections_within_distance:
                # if feature.id() in intersection.edge_ids:
                    # skip_iteration_step = True
                    # break
            
            # #skip the iteration step if necessary
            # if skip_iteration_step is True:
                # continue
            
            # #init some vars
            # linestring = feature.geometry()
            # distance = self.point.distance(linestring)
            
            # #check whether the distance is equal or less the search distance
            # if distance <= max_distance:
                # candidates.append(Candidate(linestring.nearestPoint(self.point), distance, self.id))
        
        #iterate over all lines of the network to check for candidates on this lines
        candidate_points = []
        for feature in network.vector_layer.getFeatures():
            #init some vars
            linestring = feature.geometry()
            distance = self.point.distance(linestring)
            
            #check whether the distance is equal or less the search distance
            if distance <= max_distance:
                nearest_point = linestring.nearestPoint(self.point)
                candidate_is_not_near_to_intersection = True
                
                #check, whether the possible candidate is within the range to an intersection
                for intersection in intersections_within_distance:
                    if intersection.geometry.distance(nearest_point) <= max_distance:
                        candidate_is_not_near_to_intersection = False
                        break
                
                if candidate_is_not_near_to_intersection is True:
                    candidates.append(Candidate(nearest_point, distance, self.id))
        
        #now add the intersections to the candidates
        for intersection in intersections_within_distance:
            candidates.append(Candidate(intersection.geometry, intersection.geometry.distance(self.point), self.id))
        
        return candidates
    
    def isIntersectionInSearchDistance(self, edges, candidates):
        
        for index, edge in edges.enumerate():
            
            intersection_count = 0
            intersected_edges = []
            
            for other_index, other_edge in edges.enumerate():
                
                if other_index != index:
                    if edge.geometry().intersects(other_edge.geometry()):
                        intersection_count += 1
                        intersected_edges.append(other_index)
        
        
        
        #init a variable to count the intersections
        counter = 0
        
        #iterate over all network features
        for feature in network.getFeatures():
            
            #increase the counter if the geometries intersect each other
            if feature.geometry().insersects(self.point):
                counter += 1
            
            #break up the iteration when the counter reached a specific number
            if counter == 3:
                return True
        
        return False
    


================================================
FILE: src/offlinemapmatching/mm/observation/trajectory.py
================================================
from .observation import *
from qgis.core import *

class Trajectory:
    
    def __init__(self, point_layer, id_field):
        self.observations = []
        self.extractObservations(point_layer, id_field)
        
    
    def extractObservations(self, point_layer, id_field):
        #extract all points from the QGIS layer
        features = point_layer.getFeatures()
        for feature in features:
            point = feature.geometry()
            id = feature[id_field]
            
            #create a new observation object and add them to our observation list
            obs = Observation(point, id)
            self.observations.append(obs)
    



================================================
FILE: src/offlinemapmatching/mm_processing/clip_network_algorithm.py
================================================
# -*- coding: utf-8 -*-

'''
/***************************************************************************
 Offline-MapMatching
                                 A QGIS plugin
 desciption of the plugin
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2018-08-08
        copyright            : (C) 2018 by Christoph Jung
        email                : jagodki.cj@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
'''

__author__ = 'Christoph Jung'
__date__ = '2018-08-08'
__copyright__ = '(C) 2018 by Christoph Jung'

# This will get replaced with a git SHA1 when you do a git archive

__revision__ = '$Format:%H$'
from PyQt5.QtCore import QCoreApplication, QUrl
from PyQt5.QtGui import QIcon
from qgis.core import (QgsProcessing,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterVectorLayer,
                       QgsProcessingParameterField,
                       QgsProcessingParameterString,
                       QgsProcessingParameterNumber,
                       QgsCoordinateReferenceSystem,
                       QgsProject,
                       QgsProcessingParameterFeatureSink,
                       QgsWkbTypes)
import processing
import time, os.path


class ClipNetworkAlgorithm(QgsProcessingAlgorithm):
    '''
    This is an example algorithm that takes a vector layer and
    creates a new identical one.

    It is meant to be used as an example of how to create your own
    algorithms and explain methods and variables used to do it. An
    algorithm like this will be available in all elements, and there
    is not need for additional work.

    All Processing algorithms should extend the QgsProcessingAlgorithm
    class.
    '''

    # Constants used to refer to parameters and outputs. They will be
    # used when calling the algorithm from another algorithm, or when
    # calling from the QGIS console.

    NETWORK = 'NETWORK'
    TRAJECTORY = 'TRAJECTORY'
    ORDER_FIELD = 'ORDER_FIELD'
    BUFFER_RADIUS = 'BUFFER_RADIUS'
    OUTPUT = 'OUTPUT'

    def initAlgorithm(self, config):
        '''
        Here we define the inputs and output of the algorithm, along
        with some other properties.
        '''
        
        self.addParameter(
            QgsProcessingParameterVectorLayer(
                self.NETWORK,
                self.tr('Network layer'),
                [QgsProcessing.TypeVectorLine]
            )
        )
        
        self.addParameter(
            QgsProcessingParameterVectorLayer(
                self.TRAJECTORY,
                self.tr('Trajectory layer'),
                [QgsProcessing.TypeVectorPoint]
            )
        )
        
        self.addParameter(
            QgsProcessingParameterField(
                self.ORDER_FIELD,
                self.tr('Order Trajectory by'),
                parentLayerParameterName=self.TRAJECTORY,
                type=QgsProcessingParameterField.Any
            )
        )
        
        self.addParameter(
            QgsProcessingParameterNumber(
                self.BUFFER_RADIUS,
                self.tr('Buffer radius around the Trajectory'),
                type=QgsProcessingParameterNumber.Double,
                defaultValue=0,
                minValue=0.0
            )
        )
        
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT,
                self.tr('Clipped Network')
            )
        )

    def processAlgorithm(self, parameters, context, feedback):
        '''
        Here is where the processing itself takes place.
        '''
        start_time = time.time()
        
        #extract all parameters
        network_layer = self.parameterAsVectorLayer(
            parameters,
            self.NETWORK,
            context
        )
        
        trajectory_layer = self.parameterAsVectorLayer(
            parameters,
            self.TRAJECTORY,
            context
        )
        
        trajectory_order_field = self.parameterAsString(
            parameters,
            self.ORDER_FIELD,
            context
        )
        
        buffer = self.parameterAsDouble(
            parameters,
            self.BUFFER_RADIUS,
            context
        )
        
        (sink, dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context,
            network_layer.fields(),
            QgsWkbTypes.LineString,
            network_layer.crs()
        )
        
        #init the progressbar
        max_count = 4
        counter = 0
        feedback.setProgress(int((counter / max_count) * max_count))
        
        #trajectory to path
        points_to_path = processing.run("qgis:pointstopath", {
            'INPUT':trajectory_layer,
            'ORDER_FIELD':trajectory_order_field,
            'GROUP_FIELD':None,
            'DATE_FORMAT':'',
            'OUTPUT':'memory:path'
        })
        counter += 1
        feedback.setProgress(int((counter / max_count) * max_count))
        if feedback.isCanceled():
            return {'canceled °o°': str(round(time.time() - start_time, 2))}
        
        #buffer path
        buffer = processing.run("native:buffer", {
            'INPUT':points_to_path['OUTPUT'],
            'DISTANCE':buffer,
            'SEGMENTS':5,
            'END_CAP_STYLE':0,
            'JOIN_STYLE':0,
            'MITER_LIMIT':2,
            'DISSOLVE':False,
            'OUTPUT':'memory:buffer'})
        counter += 1
        feedback.setProgress(int((counter / max_count) * max_count))
        feedback.pushInfo('created buffer successfully')
        if feedback.isCanceled():
            return {'canceled °o°': str(round(time.time() - start_time, 2))}
        
        #clip network
        clipped_network = processing.run("native:clip", {
            'INPUT':network_layer,
            'OVERLAY':buffer['OUTPUT'],
            'OUTPUT':'memory:clip'})
        counter += 1
        feedback.setProgress(int((counter / max_count) * max_count))
        feedback.pushInfo('clipped buffer successfully')
        if feedback.isCanceled():
            return {'canceled °o°': str(round(time.time() - start_time, 2))}
        
        #multipart to single part
        single_part_network = processing.run("native:multiparttosingleparts", {
            'INPUT':clipped_network['OUTPUT'],
            'OUTPUT':'memory:omm_clipped_network'})
        counter += 1
        feedback.setProgress(int((counter / max_count) * max_count))
        feedback.pushInfo('created single parts successfully')
        if feedback.isCanceled():
            return {'canceled °o°': str(round(time.time() - start_time, 2))}
        
        #add the result to the QGIS project
        sink.addFeatures(single_part_network['OUTPUT'].getFeatures())
        
        return {'OUTPUT': dest_id}

    def name(self):
        '''
        Returns the algorithm name, used for identifying the algorithm. This
        string should be fixed for the algorithm, and must not be localised.
        The name should be unique within each provider. Names should contain
        lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        '''
        return 'clip_network'
    
    def helpUrl(self):
        '''
        Returns the URL for the help document, if a help document does exist.
        '''
        dir = os.path.dirname(__file__)
        file = os.path.abspath(os.path.join(dir, '..', 'help_docs', 'help.html'))
        if not os.path.exists(file):
            return ''
        return QUrl.fromLocalFile(file).toString(QUrl.FullyEncoded)

    def shortHelpString(self):
        '''Returns the text for the help widget, if a help document does exist.'''
        dir = os.path.dirname(__file__)
        file = os.path.abspath(os.path.join(dir, '..', 'help_docs', 'help_processing_clipping_network.html'))
        if not os.path.exists(file):
            return ''
        with open(file) as helpf:
            help=helpf.read()
        return help
    
    def displayName(self):
        '''
        Returns the translated algorithm name, which should be used for any
        user-visible display of the algorithm name.
        '''
        return self.tr('Clip Network')

    def group(self):
        '''
        Returns the name of the group this algorithm belongs to. This string
        should be localised.
        '''
        return self.tr(self.groupId())
    
    def groupId(self):
        '''
        Returns the unique ID of the group this algorithm belongs to. This
        string should be fixed for the algorithm, and must not be localised.
        The group id should be unique within each provider. Group id should
        contain lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        '''
        return 'Preprocessing'
    
    def tr(self, string):
        return QCoreApplication.translate('Processing', string)

    def createInstance(self):
        return ClipNetworkAlgorithm()

    def icon(self):
        return QIcon(':/plugins/offline_map_matching/icons/clipping_icon.png')


================================================
FILE: src/offlinemapmatching/mm_processing/offline_map_matching_algorithm.py
================================================
# -*- coding: utf-8 -*-

'''
/***************************************************************************
 Offline-MapMatching
                                 A QGIS plugin
 desciption of the plugin
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2018-08-08
        copyright            : (C) 2018 by Christoph Jung
        email                : jagodki.cj@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
'''

__author__ = 'Christoph Jung'
__date__ = '2018-08-08'
__copyright__ = '(C) 2018 by Christoph Jung'

# This will get replaced with a git SHA1 when you do a git archive

__revision__ = '$Format:%H$'
from PyQt5.QtCore import QCoreApplication, QUrl
from PyQt5.QtGui import QIcon
from qgis.core import (QgsProcessing,
                       QgsFeatureSink,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFeatureSink,
                       QgsProcessingParameterVectorLayer,
                       QgsProcessingParameterField,
                       QgsProcessingParameterNumber,
                       QgsWkbTypes,
                       QgsCoordinateReferenceSystem,
                       QgsFields,
                       QgsProcessingParameterEnum)
from ..mm.map_matcher import MapMatcher
import time, os.path


class OfflineMapMatchingAlgorithm(QgsProcessingAlgorithm):
    '''
    This is an example algorithm that takes a vector layer and
    creates a new identical one.

    It is meant to be used as an example of how to create your own
    algorithms and explain methods and variables used to do it. An
    algorithm like this will be available in all elements, and there
    is not need for additional work.

    All Processing algorithms should extend the QgsProcessingAlgorithm
    class.
    '''

    # Constants used to refer to parameters and outputs. They will be
    # used when calling the algorithm from another algorithm, or when
    # calling from the QGIS console.

    NETWORK = 'NETWORK'
    TRAJECTORY = 'TRAJECTORY'
    TRAJECTORY_ID = 'TRAJECTORY_ID'
    MAX_SEARCH_DISTANCE = 'MAX_SEARCH_DISTANCE'
    TYPE = 'TYPE'
    OUTPUT = 'OUTPUT'

    def initAlgorithm(self, config):
        '''
        Here we define the inputs and output of the algorithm, along
        with some other properties.
        '''
        
        self.addParameter(
            QgsProcessingParameterVectorLayer(
                self.NETWORK,
                self.tr('Network layer'),
                [QgsProcessing.TypeVectorLine]
            )
        )
        
        self.addParameter(
            QgsProcessingParameterVectorLayer(
                self.TRAJECTORY,
                self.tr('Trajectory layer'),
                [QgsProcessing.TypeVectorPoint]
            )
        )
        
        self.addParameter(
            QgsProcessingParameterField(
                self.TRAJECTORY_ID,
                self.tr('Trajectory ID'),
                parentLayerParameterName=self.TRAJECTORY,
                type=QgsProcessingParameterField.Any
            )
        )
        
        self.addParameter(
            QgsProcessingParameterNumber(
                self.MAX_SEARCH_DISTANCE,
                self.tr('Maximum Search Distance [m]'),
                type=QgsProcessingParameterNumber.Double,
                defaultValue=20.0,
                minValue=0.0
            )
        )
        
        self.addParameter(
            QgsProcessingParameterEnum(
                self.TYPE,
                self.tr('Matching type'),
                options=["Based on network routing (slow)", "Based on euklidean distances (fast)"]
            )
        )
        
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT,
                self.tr('Matched Trajectory')
            )
        )

    def processAlgorithm(self, parameters, context, feedback):
        '''
        Here is where the processing itself takes place.
        '''
        start_time = time.time()
        mm = MapMatcher()
        
        #create a QgsFields-object
        attrs = mm.defineAttributes()
        fields = QgsFields()
        for field in attrs:
            fields.append(field)
        
        #extract all parameters
        network_layer = self.parameterAsVectorLayer(
            parameters,
            self.NETWORK,
            context
        )
        
        trajectory_layer = self.parameterAsVectorLayer(
            parameters,
            self.TRAJECTORY,
            context
        )
        
        trajectory_id = self.parameterAsString(
            parameters,
            self.TRAJECTORY_ID,
            context
        )
        
        max_search_distance = self.parameterAsDouble(
            parameters,
            self.MAX_SEARCH_DISTANCE,
            context
        )
        
        matching_type = self.parameterAsEnum(
            parameters,
            self.TYPE,
            context
        )
        
        #check the CRS of the input layers
        network_crs = network_layer.sourceCrs().authid()
        trajectory_crs = trajectory_layer.sourceCrs().authid()
        if network_crs != trajectory_crs:
            raise ValueError("The CRS of the both input layers are different (" + network_crs + " and " + trajectory_crs + ").")
        
        (sink, dest_id) = self.parameterAsSink(
            parameters, self.OUTPUT,
            context,
            fields,
            QgsWkbTypes.LineString,
            network_layer.sourceCrs()
        )
        
        #check the inputs
        if network_layer is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.NETWORK))
        if trajectory_layer is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.TRAJECTORY))
        if trajectory_id is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.TRAJECTORY_ID))
        if matching_type is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.TYPE))
		
        error_code = mm.startViterbiMatchingProcessing(trajectory_layer,
                                                       network_layer,
                                                       trajectory_id,
                                                       max_search_distance,
                                                       sink,
                                                       feedback,
                                                       matching_type == 1)
        
        return {'OUTPUT': dest_id,
                'ERROR_CODE': error_code,
                'COMPUTATION_TIME': str(round(time.time() - start_time, 2))}

    def name(self):
        '''
        Returns the algorithm name, used for identifying the algorithm. This
        string should be fixed for the algorithm, and must not be localised.
        The name should be unique within each provider. Names should contain
        lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        '''
        return 'match_trajectory'
    
    def helpUrl(self):
        '''
        Returns the URL for the help document, if a help document does exist.
        '''
        dir = os.path.dirname(__file__)
        file = os.path.abspath(os.path.join(dir, '..', 'help_docs', 'help.html'))
        if not os.path.exists(file):
            return ''
        return QUrl.fromLocalFile(file).toString(QUrl.FullyEncoded)

    def shortHelpString(self):
        '''Returns the text for the help widget, if a help document does exist.'''
        dir = os.path.dirname(__file__)
        file = os.path.abspath(os.path.join(dir, '..', 'help_docs', 'help_processing_match_trajectory.html'))
        if not os.path.exists(file):
            return ''
        with open(file) as helpf:
            help=helpf.read()
        return help
    
    def displayName(self):
        '''
        Returns the translated algorithm name, which should be used for any
        user-visible display of the algorithm name.
        '''
        return self.tr('Match Trajectory')

    def group(self):
        '''
        Returns the name of the group this algorithm belongs to. This string
        should be localised.
        '''
        return self.tr(self.groupId())
    
    def groupId(self):
        '''
        Returns the unique ID of the group this algorithm belongs to. This
        string should be fixed for the algorithm, and must not be localised.
        The group id should be unique within each provider. Group id should
        contain lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        '''
        return 'Matching'
    
    def tr(self, string):
        return QCoreApplication.translate('Processing', string)

    def createInstance(self):
        return OfflineMapMatchingAlgorithm()

    def icon(self):
        return QIcon(':/plugins/offline_map_matching/icons/icon.png')


================================================
FILE: src/offlinemapmatching/mm_processing/offline_map_matching_provider.py
================================================
# -*- coding: utf-8 -*-

'''
/***************************************************************************
 OfflineMapMatcher
                                 A QGIS plugin
 desciption of the plugin
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2018-08-08
        copyright            : (C) 2018 by Christoph Jung
        email                : jagodki.cj@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
'''

__author__ = 'Christoph Jung'
__date__ = '2018-08-08'
__copyright__ = '(C) 2018 by Christoph Jung'

# This will get replaced with a git SHA1 when you do a git archive

__revision__ = '$Format:%H$'

from qgis.core import QgsProcessingProvider
from PyQt5.QtGui import QIcon
from .offline_map_matching_algorithm import OfflineMapMatchingAlgorithm
from .clip_network_algorithm import ClipNetworkAlgorithm
from .reduce_trajectory_density import ReduceTrajectoryDensity
# from .fast_trajectory_matching import FastTrajectoryMatching


class OfflineMapMatchingProvider(QgsProcessingProvider):

    def __init__(self):
        QgsProcessingProvider.__init__(self)

        # Load algorithms
        self.alglist = [OfflineMapMatchingAlgorithm(),
                        #FastTrajectoryMatching(),
                        ClipNetworkAlgorithm(),
						ReduceTrajectoryDensity()]

    def unload(self):
        '''
        Unloads the provider. Any tear-down steps required by the provider
        should be implemented here.
        '''
        pass

    def loadAlgorithms(self):
        '''
        Loads all algorithms belonging to this provider.
        '''
        for alg in self.alglist:
            self.addAlgorithm( alg )

    def id(self):
        '''
        Returns the unique provider id, used for identifying the provider. This
        string should be a unique, short, character only string, eg 'qgis' or
        'gdal'. This string should not be localised.
        '''
        return 'omm'

    def icon(self):
        return QIcon(':/plugins/offline_map_matching/icons/icon.png')
    
    def name(self):
        '''
        Returns the provider name, which is used to describe the provider
        within the GUI.

        This string should be short (e.g. 'Lastools') and localised.
        '''
        return self.tr('Offline-MapMatching')

    def longName(self):
        '''
        Returns the a longer version of the provider name, which can include
        extra details such as version numbers. E.g. 'Lastools LIDAR tools
        (version 2.2.1)'. This string should be localised. The default
        implementation returns the same string as name().
        '''
        return self.name()

================================================
FILE: src/offlinemapmatching/mm_processing/reduce_trajectory_density.py
================================================
# -*- coding: utf-8 -*-

'''
/***************************************************************************
 Offline-MapMatching
                                 A QGIS plugin
 desciption of the plugin
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2019-11-04
        copyright            : (C) 2019 by Christoph Jung
        email                : jagodki.cj@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
'''

__author__ = 'Christoph Jung'
__date__ = '2019-11-04'
__copyright__ = '(C) 2019 by Christoph Jung'

# This will get replaced with a git SHA1 when you do a git archive

__revision__ = '$Format:%H$'
from PyQt5.QtCore import QCoreApplication, QUrl
from PyQt5.QtGui import QIcon
from qgis.core import (QgsProcessing,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterVectorLayer,
                       QgsProcessingParameterField,
                       QgsProcessingParameterString,
                       QgsProcessingParameterNumber,
                       QgsCoordinateReferenceSystem,
                       QgsProject,
                       QgsProcessingParameterFeatureSink,
                       QgsWkbTypes,
                       QgsProcessingParameterBoolean)
import processing
import time, os.path


class ReduceTrajectoryDensity(QgsProcessingAlgorithm):
    '''
    This is an example algorithm that takes a vector layer and
    creates a new identical one.

    It is meant to be used as an example of how to create your own
    algorithms and explain methods and variables used to do it. An
    algorithm like this will be available in all elements, and there
    is not need for additional work.

    All Processing algorithms should extend the QgsProcessingAlgorithm
    class.
    '''

    # Constants used to refer to parameters and outputs. They will be
    # used when calling the algorithm from another algorithm, or when
    # calling from the QGIS console.

    TRAJECTORY = 'TRAJECTORY'
    KEEP_LAST_FEATURE = 'KEEP_LAST_FEATURE'
    DISTANCE = 'DISTANCE'
    OUTPUT = 'OUTPUT'

    def initAlgorithm(self, config):
        '''
        Here we define the inputs and output of the algorithm, along
        with some other properties.
        '''
        
        self.addParameter(
            QgsProcessingParameterVectorLayer(
                self.TRAJECTORY,
                self.tr('Trajectory Layer'),
                [QgsProcessing.TypeVectorPoint]
            )
        )
        
        self.addParameter(
            QgsProcessingParameterBoolean(
                self.KEEP_LAST_FEATURE,
                self.tr('Keep the last feature of the trajectory'),
                optional=True
            )
        )
        
        self.addParameter(
            QgsProcessingParameterNumber(
                self.DISTANCE,
                self.tr('Minimum distance between two consecutive points'),
                type=QgsProcessingParameterNumber.Double,
                defaultValue=100,
                minValue=0.0
            )
        )
        
        self.addParameter(
            QgsProcessingParameterFeatureSink(
                self.OUTPUT,
                self.tr('Reduced Trajectory')
            )
        )

    def processAlgorithm(self, parameters, context, feedback):
        '''
        Here is where the processing itself takes place.
        '''
        start_time = time.time()
        
        #extract all parameters
        trajectory_layer = self.parameterAsVectorLayer(
            parameters,
            self.TRAJECTORY,
            context
        )
        
        distance = self.parameterAsDouble(
            parameters,
            self.DISTANCE,
            context
        )
        
        keep_last_feature = self.parameterAsBool(
            parameters,
            self.KEEP_LAST_FEATURE,
            context
        )
        
        (output, dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context,
            trajectory_layer.fields(),
            QgsWkbTypes.Point,
            trajectory_layer.crs()
        )
        
        #check the inputs
        if trajectory_layer is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.TRAJECTORY))
        if distance is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.DISTANCE))
        if keep_last_feature is None:
            keep_last_feature = False
        
        #present some information to the user
        feedback.pushInfo('CRS of the trajectory is {}'.format(trajectory_layer.sourceCrs().authid()))
        
        #init the progressbar
        feedback.setProgress(0)
        
        #reduce the trajectory density
        result = self.reduceDensity(0, 1, trajectory_layer, distance, output, feedback, trajectory_layer.featureCount(), keep_last_feature)
        
        return {'OUTPUT': dest_id}

    def name(self):
        '''
        Returns the algorithm name, used for identifying the algorithm. This
        string should be fixed for the algorithm, and must not be localised.
        The name should be unique within each provider. Names should contain
        lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        '''
        return 'reduce_trajectory_density'
    
    def helpUrl(self):
        '''
        Returns the URL for the help document, if a help document does exist.
        '''
        dir = os.path.dirname(__file__)
        file = os.path.abspath(os.path.join(dir, '..', 'help_docs', 'help.html'))
        if not os.path.exists(file):
            return ''
        return QUrl.fromLocalFile(file).toString(QUrl.FullyEncoded)

    def shortHelpString(self):
        '''Returns the text for the help widget, if a help document does exist.'''
        dir = os.path.dirname(__file__)
        file = os.path.abspath(os.path.join(dir, '..', 'help_docs', 'help_processing_reduce_density.html'))
        if not os.path.exists(file):
            return ''
        with open(file) as helpf:
            help=helpf.read()
        return help
    
    def displayName(self):
        '''
        Returns the translated algorithm name, which should be used for any
        user-visible display of the algorithm name.
        '''
        return self.tr('Reduce Trajectory Density')

    def group(self):
        '''
        Returns the name of the group this algorithm belongs to. This string
        should be localised.
        '''
        return self.tr(self.groupId())
    
    def groupId(self):
        '''
        Returns the unique ID of the group this algorithm belongs to. This
        string should be fixed for the algorithm, and must not be localised.
        The group id should be unique within each provider. Group id should
        contain lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        '''
        return 'Preprocessing'
    
    def tr(self, string):
        return QCoreApplication.translate('Processing', string)

    def createInstance(self):
        return ReduceTrajectoryDensity()

    def icon(self):
        return QIcon(':/plugins/offline_map_matching/icons/reduce_density_icon.png')
    
    def reduceDensity(self, startIndex, nextIndex, layer, distance, output, feedback, feature_count, keep_last_feature):
        #handle a pressed cancel button and the progressbar
        feedback.setProgress(int(startIndex / feature_count) * 100)
        if feedback.isCanceled():
            return -99
        
        #init some variables for the following iteration
        start_feature = None
        trajectory_features = layer.getFeatures()
        
        for index, feature in enumerate(trajectory_features):
            
            #set the starting feature if none and skip to the next loop
            if start_feature is None:
                start_feature = feature
                output.addFeature(feature)
                continue
                
            #calculate distance, adjust the starting feature after it if distance is big enough
            if start_feature.geometry().distance(feature.geometry()) >= distance:
                    output.addFeature(feature)
                    start_feature = feature
            
            #check, whether we reached the last feature and keep the last vertex if necessary
            elif keep_last_feature and index == (layer.featureCount() - 1):
                output.addFeature(feature)
        
        return -99
        
    


================================================
FILE: src/offlinemapmatching/offline_map_matching.py
================================================
# -*- coding: utf-8 -*-
'''
/***************************************************************************
 OfflineMapMatching
                                 A QGIS plugin
a QGIS-plugin for matching a trajectory with a network using a Hidden Markov Model and Viterbi algorithm
Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2018-06-14
        git sha              : $Format:%H$
        copyright            : (C) 2018 by Christoph Jung
        email                : jagodki.cj@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
'''
from PyQt5.QtCore import QSettings, QTranslator, qVersion, QCoreApplication
from PyQt5.QtGui import QIcon, QTextCursor
from PyQt5.QtWidgets import QAction, QMenu
from qgis.gui import QgsMessageBar
from qgis.core import *
import time, traceback, sys, inspect, processing

# Initialize Qt resources from file resources.py
from .resources import *
# Import the code for the dialog
from .offline_map_matching_dialog import OfflineMapMatchingDialog
import os.path

#import own classes
from .mm.map_matcher import *
from .mm_processing.offline_map_matching_provider import *
'''
cmd_folder = os.path.split(inspect.getfile(inspect.currentframe()))[0]

if cmd_folder not in sys.path:
    sys.path.insert(0, cmd_folder)
'''
class OfflineMapMatching:
    '''QGIS Plugin Implementation.'''

    def __init__(self, iface):
        '''Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        '''
        
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            'OfflineMapMatching_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)

            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

        # Create the dialog (after translation) and keep reference
        self.dlg = OfflineMapMatchingDialog()

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&Offline-MapMatching')
        # TODO: We are going to let the user set this up in a future iteration
        self.toolbar = self.iface.addToolBar(u'OfflineMapMatching')
        self.toolbar.setObjectName(u'OfflineMapMatching')
        
        #add help-document to the GUI
        dir = os.path.dirname(__file__)
        file = os.path.abspath(os.path.join(dir, 'help_docs', 'help.html'))
        if os.path.exists(file):
            with open(file) as helpf:
                help = helpf.read()
                self.dlg.textBrowser_help.insertHtml(help)
                self.dlg.textBrowser_help.moveCursor(QTextCursor.Start)
        
        #declare additional instance vars
        self.map_matcher = MapMatcher()
        self.provider = OfflineMapMatchingProvider()
        
        #connect slots and signals
        self.dlg.comboBox_trajectory.currentIndexChanged.connect(self.startPopulateFieldsComboBox)
        self.dlg.pushButton_start.clicked.connect(self.startMapMatching)
        
        #set a default crs to avoid problems in QGIS 3.4
        self.dlg.mQgsProjectionSelectionWidget.setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        '''Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        '''
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('OfflineMapMatching', message)


    def add_action(
        self,
        icon_path,
        text,
        callback=None,
        enabled_flag=True,
        add_to_menu=True,
        add_to_toolbar=True,
        status_tip=None,
        whats_this=None,
        parent=None,
        action=None):
        '''Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        '''

        icon = QIcon(icon_path)
        if action is None:
            action = QAction(icon, text, parent)
            action.triggered.connect(callback)
            action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            self.toolbar.addAction(action)

        if add_to_menu:
            self.iface.addPluginToVectorMenu(
                self.menu,
                action)

#        self.iface.vectorMenu().addAction(action)
        
        self.actions.append(action)

        return action

    def initGui(self):
        '''Create the menu entries, toolbar icons inside the QGIS GUI and add a new processing provider.'''
        icon_path = ':/plugins/offline_map_matching/icons/icon.png'
        
        #set up entries for the gui
        self.add_action(
            icon_path,
            text=self.tr(u'Match Trajectory'),
            callback=self.matchTrajectory,
            parent=self.iface.mainWindow())
        
        #init the preprocessing group with their entries
        menu = QMenu()
        
        #add icons
        icon_clip = QIcon(':/plugins/offline_map_matching/icons/clipping_icon.png')
        icon_density = QIcon(':/plugins/offline_map_matching/icons/reduce_density_icon.png')
        icon_pp = QIcon(':/plugins/offline_map_matching/icons/preprocessing_icon.png')
        
        #add actions
        action_clip = menu.addAction(icon_clip, 'Clip Network', self.clipNetwork)
        action_clip.setObjectName('clip_network')
        
        action_reduce = menu.addAction(icon_density, 'Reduce Trajectory Density', self.reduceDensity)
        action_reduce.setObjectName('reduce_density')
        
        #add main entry
        preprocessing_action = QAction(icon_pp, 'Preprocessing', self.iface.mainWindow())
        preprocessing_action.setMenu(menu)
        self.add_action(
            '',
            text=self.tr(u'Preprocessing'),
            action=preprocessing_action,
            parent=self.iface.mainWindow(),
            add_to_toolbar=False)
        
#        preprocessing_action.setMenu(menu)
#        self.actions.append(preprocessing_action)
#        self.iface.addPluginToVectorMenu(self.menu, preprocessing_action)
        
        #add the processing provider
        QgsApplication.processingRegistry().addProvider(self.provider)

    
    def clipNetwork(self):
        processing.execAlgorithmDialog('omm:clip_network', {})
    
    def reduceDensity(self):
        processing.execAlgorithmDialog('omm:reduce_trajectory_density', {})
        
    def fastTrajectoryMatching(self):
        processing.execAlgorithmDialog('omm:fast_trajectory_matching', {})
    
    def matchTrajectory(self):
        processing.execAlgorithmDialog('omm:match_trajectory', {})
    
    def unload(self):
        '''Removes the plugin menu item and icon from QGIS GUI. Remove the processing provider.'''
        for action in self.actions:
            self.iface.removePluginVectorMenu(
                self.tr(u'&Offline-MapMatching'),
                action)
            #self.iface.vectorMenu().removeAction(action)
            self.iface.removeToolBarIcon(action)
        # remove the toolbar
        del self.toolbar
        
        #remove the processing provider
        QgsApplication.processingRegistry().removeProvider(self.provider)


    def run(self):
        '''Run method that performs all the real work'''
        #populate the comboboxes with the available layers
        self.populateComboBox('network')
        self.populateComboBox('trajectory')
        self.populateComboBox('fields')
        
        #clear all other gui elements
        self.dlg.progressBar.setValue(0)
        self.dlg.doubleSpinBox_sigma.setValue(50.0)
        self.dlg.doubleSpinBox_my.setValue(0.0)
        self.dlg.doubleSpinBox_beta.setValue(30.0)
        self.dlg.doubleSpinBox_max.setValue(0.0)
        self.dlg.label_info.setText('')
        #self.dlg.lineEdit_crs.setText('')
        
        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        #result = self.dlg.exec_()
        # See if OK was pressed
        #if result:
            # Do something useful here - delete the line containing pass and
            # substitute with your code.
            #pass
    
    def populateComboBox(self, type):
        '''Populate the given combobox.'''
        if type == 'network':
            self.map_matcher.fillLayerComboBox(self.iface, self.dlg.comboBox_network, 'LINESTRING')
        elif type == 'trajectory':
            self.map_matcher.fillLayerComboBox(self.iface, self.dlg.comboBox_trajectory, 'POINT')
        elif type == 'fields':
            self.map_matcher.fillAttributeComboBox(self.dlg.comboBox_trajectoryID, self.dlg.comboBox_trajectory.currentText())

    def startPopulateFieldsComboBox(self):
        self.populateComboBox('fields')
    
    def startMapMatching(self):
        self.dlg.groupBox_data.setEnabled(False)
        self.dlg.groupBox_settings.setEnabled(False)
        self.dlg.pushButton_start.setEnabled(False)
        
        try:
            start_time = time.time()
            result = self.map_matcher.startViterbiMatchingGui(
                          self.dlg.progressBar,
                          self.dlg.comboBox_trajectory.currentText(),
                          self.dlg.comboBox_network.currentText(),
                          self.dlg.comboBox_trajectoryID.currentText(),
                          self.dlg.doubleSpinBox_sigma.value(),
                          self.dlg.doubleSpinBox_my.value(),
                          self.dlg.doubleSpinBox_beta.value(),
                          self.dlg.doubleSpinBox_max.value(),
                          self.dlg.label_info,
                          self.dlg.mQgsProjectionSelectionWidget.crs().authid())
            
            if result == 0:
                self.iface.messageBar().pushMessage('map matching finished ^o^ - time: ' + str(round(time.time() - start_time, 2)) + ' sec', level=Qgis.Success, duration=60)
            elif result == -1:
                self.iface.messageBar().pushMessage('Error during calculation of candidates. Check the QGIS-log for further information.', level=Qgis.Warning, duration=60)
            elif result == -2:
                self.iface.messageBar().pushMessage('Error during calculation of starting probabilities. Check the QGIS-log for further information.', level=Qgis.Warning, duration=60)
            elif result == -3:
                self.iface.messageBar().pushMessage('Error during calculation of transition probabilities. Check the QGIS-log for further information.', level=Qgis.Warning, duration=60)
            elif result == -4:
                self.iface.messageBar().pushMessage('Error during calculation of backtracking. Check the QGIS-log for further information.', level=Qgis.Warning, duration=60)
            elif result == -5:
                self.iface.messageBar().pushMessage('Error during calculating the most likely path. Check the QGIS-log for further information.', level=Qgis.Warning, duration=60)
            elif result == -6:
                self.iface.messageBar().pushMessage('Error during calculating the path on network. Check the QGIS-log for further information.', level=Qgis.Warning, duration=60)
    
        except:
            QgsMessageLog.logMessage(traceback.format_exc(), level=Qgis.Critical)
            self.iface.messageBar().pushMessage('An error occured. Please look into the log and/or Python console for further information.', level=Qgis.Critical, duration=60)
        
        
        self.dlg.groupBox_data.setEnabled(True)
        self.dlg.groupBox_settings.setEnabled(True)
        self.dlg.pushButton_start.setEnabled(True)

================================================
FILE: src/offlinemapmatching/offline_map_matching_dialog.py
================================================
# -*- coding: utf-8 -*-
"""
/***************************************************************************
 OfflineMapMatchingDialog
                                 A QGIS plugin
 a QGIS-plugin for matching a trajectory with a network using a Hidden Markov Model and Viterbi algorithm
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                             -------------------
        begin                : 2018-06-14
        git sha              : $Format:%H$
        copyright            : (C) 2018 by Christoph Jung
        email                : jagodki.cj@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""

import os

from PyQt5 import uic
from PyQt5 import QtWidgets

FORM_CLASS, _ = uic.loadUiType(os.path.join(
    os.path.dirname(__file__), 'offline_map_matching_dialog_base.ui'))


class OfflineMapMatchingDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Constructor."""
        super(OfflineMapMatchingDialog, self).__init__(parent)
        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)


================================================
FILE: src/offlinemapmatching/offline_map_matching_dialog_base.ui
================================================
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>OfflineMapMatchingDialogBase</class>
 <widget class="QDialog" name="OfflineMapMatchingDialogBase">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>679</width>
    <height>472</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Match Trajectory</string>
  </property>
  <layout class="QHBoxLayout" name="horizontalLayout">
   <item>
    <layout class="QVBoxLayout" name="verticalLayout_3">
     <item>
      <widget class="QGroupBox" name="groupBox_data">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="title">
        <string>Data</string>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout_2">
        <item>
         <layout class="QGridLayout" name="gridLayout">
          <item row="0" column="0">
           <widget class="QLabel" name="label_network">
            <property name="text">
             <string>Network-Layer:</string>
            </property>
            <property name="alignment">
             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
            </property>
           </widget>
          </item>
          <item row="0" column="1">
           <widget class="QComboBox" name="comboBox_network">
            <property name="sizePolicy">
             <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
              <horstretch>0</horstretch>
              <verstretch>0</verstretch>
             </sizepolicy>
            </property>
           </widget>
          </item>
          <item row="1" column="0">
           <widget class="QLabel" name="label_trajectory">
            <property name="text">
             <string>Trajectory-Layer:</string>
            </property>
            <property name="alignment">
             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
            </property>
           </widget>
          </item>
          <item row="1" column="1">
           <widget class="QComboBox" name="comboBox_trajectory">
            <property name="sizePolicy">
             <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
              <horstretch>0</horstretch>
              <verstretch>0</verstretch>
             </sizepolicy>
            </property>
           </widget>
          </item>
          <item row="2" column="0">
           <widget class="QLabel" name="label_trajectoryID">
            <property name="text">
             <string>Trajectory-ID:</string>
            </property>
            <property name="alignment">
             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
            </property>
           </widget>
          </item>
          <item row="2" column="1">
           <widget class="QComboBox" name="comboBox_trajectoryID">
            <property name="sizePolicy">
             <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
              <horstretch>0</horstretch>
              <verstretch>0</verstretch>
             </sizepolicy>
            </property>
           </widget>
          </item>
         </layout>
        </item>
       </layout>
      </widget>
     </item>
     <item>
      <widget class="QGroupBox" name="groupBox_settings">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="title">
        <string>Settings</string>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout_5">
        <item>
         <layout class="QGridLayout" name="gridLayout_2">
          <item row="3" column="0">
           <widget class="QLabel" name="label_my">
            <property name="text">
             <string>Expected Value:</string>
            </property>
            <property name="alignment">
             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
            </property>
           </widget>
          </item>
          <item row="3" column="1">
           <widget class="QDoubleSpinBox" name="doubleSpinBox_my">
            <property name="maximum">
             <double>9900000000.989999771118164</double>
            </property>
           </widget>
          </item>
          <item row="4" column="0">
           <widget class="QLabel" name="label_beta">
            <property name="text">
             <string>Transition Weight:</string>
            </property>
            <property name="alignment">
             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
            </property>
           </widget>
          </item>
          <item row="2" column="1">
           <widget class="QDoubleSpinBox" name="doubleSpinBox_sigma">
            <property name="maximum">
             <double>9900000000.989999771118164</double>
            </property>
            <property name="value">
             <double>50.000000000000000</double>
            </property>
           </widget>
          </item>
          <item row="1" column="1">
           <widget class="QDoubleSpinBox" name="doubleSpinBox_max">
            <property name="sizePolicy">
             <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
              <horstretch>0</horstretch>
              <verstretch>0</verstretch>
             </sizepolicy>
            </property>
            <property name="maximum">
             <double>9900000000.989999771118164</double>
            </property>
            <property name="value">
             <double>20.000000000000000</double>
            </property>
           </widget>
          </item>
          <item row="2" column="0">
           <widget class="QLabel" name="label_sigma">
            <property name="text">
             <string>Standard Deviation:</string>
            </property>
            <property name="alignment">
             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
            </property>
           </widget>
          </item>
          <item row="4" column="1">
           <widget class="QDoubleSpinBox" name="doubleSpinBox_beta">
            <property name="maximum">
             <double>9900000000.989999771118164</double>
            </property>
            <property name="value">
             <double>30.000000000000000</double>
            </property>
           </widget>
          </item>
          <item row="1" column="0">
           <widget class="QLabel" name="label_max">
            <property name="sizePolicy">
             <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
              <horstretch>0</horstretch>
              <verstretch>0</verstretch>
             </sizepolicy>
            </property>
            <property name="text">
             <string>max. Search Distance [m]:</string>
            </property>
            <property name="alignment">
             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
            </property>
           </widget>
          </item>
          <item row="0" column="0">
           <widget class="QLabel" name="label_crs">
            <property name="text">
             <string>CRS of input layers:</string>
            </property>
            <property name="alignment">
             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
            </property>
           </widget>
          </item>
          <item row="0" column="1">
           <widget class="QgsProjectionSelectionWidget" name="mQgsProjectionSelectionWidget" native="true"/>
          </item>
         </layout>
        </item>
       </layout>
      </widget>
     </item>
     <item>
      <spacer name="verticalSpacer">
       <property name="orientation">
        <enum>Qt::Vertical</enum>
       </property>
       <property name="sizeHint" stdset="0">
        <size>
         <width>20</width>
         <height>40</height>
        </size>
       </property>
      </spacer>
     </item>
     <item>
      <widget class="QLabel" name="label_info">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="text">
        <string>Ready to start calculation</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QProgressBar" name="progressBar">
       <property name="value">
        <number>0</number>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QPushButton" name="pushButton_start">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="text">
        <string>start map matching</string>
       </property>
      </widget>
     </item>
    </layout>
   </item>
   <item>
    <layout class="QVBoxLayout" name="verticalLayout">
     <item>
      <widget class="QTextBrowser" name="textBrowser_help">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Maximum" vsizetype="Expanding">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="readOnly">
        <bool>true</bool>
       </property>
       <property name="openExternalLinks">
        <bool>true</bool>
       </property>
      </widget>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <customwidgets>
  <customwidget>
   <class>QgsProjectionSelectionWidget</class>
   <extends>QWidget</extends>
   <header>qgsprojectionselectionwidget.h</header>
  </customwidget>
 </customwidgets>
 <tabstops>
  <tabstop>comboBox_network</tabstop>
  <tabstop>comboBox_trajectory</tabstop>
  <tabstop>comboBox_trajectoryID</tabstop>
  <tabstop>doubleSpinBox_max</tabstop>
  <tabstop>doubleSpinBox_sigma</tabstop>
  <tabstop>doubleSpinBox_my</tabstop>
  <tabstop>doubleSpinBox_beta</tabstop>
  <tabstop>pushButton_start</tabstop>
  <tabstop>textBrowser_help</tabstop>
 </tabstops>
 <resources/>
 <connections/>
</ui>


================================================
FILE: src/offlinemapmatching/pb_tool.cfg
================================================
#/***************************************************************************
# OfflineMapMatching
#
# Configuration file for plugin builder tool (pb_tool)
# Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
#                            -------------------
#       begin               : 2018-06-14
#       copyright           : (C) 2018 by Christoph Junh
#       email               : jagodki.cj@gmail.com
# ***************************************************************************/
#
#/***************************************************************************
# *                                                                         *
# *   This program is free software; you can redistribute it and/or modify  *
# *   it under the terms of the GNU General Public License as published by  *
# *   the Free Software Foundation; either version 2 of the License, or     *
# *   (at your option) any later version.                                   *
# *                                                                         *
# ***************************************************************************/
# 
# 
# You can install pb_tool using:
#  pip install http://geoapt.net/files/pb_tool.zip
# 
# Consider doing your development (and install of pb_tool) in a virtualenv.
# 
# For details on setting up and using pb_tool, see:
#  http://g-sherman.github.io/plugin_build_tool/
#
# Issues and pull requests here:
# https://github.com/g-sherman/plugin_build_tool:
# 
# Sane defaults for your plugin generated by the Plugin Builder are
# already set below.
# 
# As you add Python source files and UI files to your plugin, add
# them to the appropriate [files] section below.

[plugin]
# Name of the plugin. This is the name of the directory that will
# be created in .qgis2/python/plugins
name: Offline-MapMatching

# Full path to where you want your plugin directory copied. If empty,
# the QGIS default path will be used. Don't include the plugin name in
# the path.
plugin_path: /Users/christophjung/Library/Application Support/QGIS/QGIS3/profiles/default/python/plugins

[files]
# Python  files that should be deployed with the plugin
python_files: __init__.py offline_map_matching.py offline_map_matching_dialog.py

# The main dialog file that is loaded (not compiled)
main_dialog: offline_map_matching_dialog_base.ui

# Other ui files for dialogs you create (these will be compiled)
compiled_ui_files: 

# Resource file(s) that will be compiled
resource_files: resources.qrc

# Other files required for the plugin
extras: metadata.txt style.qml

# Other directories to be deployed with the plugin.
# These must be subdirectories under the plugin directory
extra_dirs: mm mm_processing help_docs icons

# ISO code(s) for any locales (translations), separated by spaces.
# Corresponding .ts files must exist in the i18n directory
locales:

[help]
# the built help directory that should be deployed with the plugin
#dir: help/build/html
# the name of the directory to target in the deployed plugin 
#target: help


================================================
FILE: src/offlinemapmatching/resources.py
================================================
# -*- coding: utf-8 -*-

# Resource object code
#
# Created by: The Resource Compiler for PyQt5 (Qt v5.14.0)
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore

qt_resource_data = b"\
\x00\x00\x13\x5d\
\x89\
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
\x00\x00\x31\x00\x00\x00\x2d\x08\x06\x00\x00\x00\x07\x88\x83\xfc\
\x00\x00\x01\x61\x69\x43\x43\x50\x6b\x43\x47\x43\x6f\x6c\x6f\x72\
\x53\x70\x61\x63\x65\x44\x69\x73\x70\x6c\x61\x79\x50\x33\x00\x00\
\x28\x91\x63\x60\x60\x52\x49\x2c\x28\xc8\x61\x61\x60\x60\xc8\xcd\
\x2b\x29\x0a\x72\x77\x52\x88\x88\x8c\x52\x60\x7f\xc8\xc0\x0e\x84\
\xbc\x0c\x62\x0c\x0a\x89\xc9\xc5\x05\x8e\x01\x01\x3e\x40\x25\x0c\
\x30\x1a\x15\x7c\xbb\xc6\xc0\x08\xa2\x2f\xeb\x82\xcc\x3a\x25\x35\
\xb5\x49\xb5\x5e\xc0\xd7\x62\xa6\xf0\xd5\x8b\xaf\x44\x9b\x30\xd5\
\xa3\x00\xae\x94\xd4\xe2\x64\x20\xfd\x07\x88\x53\x93\x0b\x8a\x4a\
\x18\x18\x18\x53\x80\x6c\xe5\xf2\x92\x02\x10\xbb\x03\xc8\x16\x29\
\x02\x3a\x0a\xc8\x9e\x03\x62\xa7\x43\xd8\x1b\x40\xec\x24\x08\xfb\
\x08\x58\x4d\x48\x90\x33\x90\x7d\x03\xc8\x56\x48\xce\x48\x04\x9a\
\xc1\xf8\x03\xc8\xd6\x49\x42\x12\x4f\x47\x62\x43\xed\x05\x01\x6e\
\x97\xcc\xe2\x82\x9c\xc4\x4a\x85\x00\x63\x02\xae\x25\x03\x94\xa4\
\x56\x94\x80\x68\xe7\xfc\x82\xca\xa2\xcc\xf4\x8c\x12\x05\x47\x60\
\x28\xa5\x2a\x78\xe6\x25\xeb\xe9\x28\x18\x19\x18\x9a\x33\x30\x80\
\xc2\x1c\xa2\xfa\x73\x20\x38\x2c\x19\xc5\xce\x20\xc4\x9a\xef\x33\
\x30\xd8\xee\xff\xff\xff\xff\x6e\x84\x98\xd7\x7e\x06\x86\x8d\x40\
\x9d\x5c\x3b\x11\x62\x1a\x16\x0c\x0c\x82\xdc\x0c\x0c\x27\x76\x16\
\x24\x16\x25\x82\x85\x98\x81\x98\x29\x2d\x8d\x81\xe1\xd3\x72\x06\
\x06\xde\x48\x06\x06\xe1\x0b\x40\x3d\xd1\xc5\x69\xc6\x46\x60\x79\
\x46\x1e\x27\x06\x06\xd6\x7b\xff\xff\x7f\x56\x63\x60\x60\x9f\xcc\
\xc0\xf0\x77\xc2\xff\xff\xbf\x17\xfd\xff\xff\x77\x31\x50\xf3\x1d\
\x06\x86\x03\x79\x00\x15\x21\x65\xee\x35\xc7\xd1\xfb\x00\x00\x01\
\x59\x69\x54\x58\x74\x58\x4d\x4c\x3a\x63\x6f\x6d\x2e\x61\x64\x6f\
\x62\x65\x2e\x78\x6d\x70\x00\x00\x00\x00\x00\x3c\x78\x3a\x78\x6d\
\x70\x6d\x65\x74\x61\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x3d\x22\x61\
\x64\x6f\x62\x65\x3a\x6e\x73\x3a\x6d\x65\x74\x61\x2f\x22\x20\x78\
\x3a\x78\x6d\x70\x74\x6b\x3d\x22\x58\x4d\x50\x20\x43\x6f\x72\x65\
\x20\x35\x2e\x34\x2e\x30\x22\x3e\x0a\x20\x20\x20\x3c\x72\x64\x66\
\x3a\x52\x44\x46\x20\x78\x6d\x6c\x6e\x73\x3a\x72\x64\x66\x3d\x22\
\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\
\x67\x2f\x31\x39\x39\x39\x2f\x30\x32\x2f\x32\x32\x2d\x72\x64\x66\
\x2d\x73\x79\x6e\x74\x61\x78\x2d\x6e\x73\x23\x22\x3e\x0a\x20\x20\
\x20\x20\x20\x20\x3c\x72\x64\x66\x3a\x44\x65\x73\x63\x72\x69\x70\
\x74\x69\x6f\x6e\x20\x72\x64\x66\x3a\x61\x62\x6f\x75\x74\x3d\x22\
\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x78\x6d\
\x6c\x6e\x73\x3a\x74\x69\x66\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\
\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x74\x69\
\x66\x66\x2f\x31\x2e\x30\x2f\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\
\x20\x20\x20\x3c\x74\x69\x66\x66\x3a\x4f\x72\x69\x65\x6e\x74\x61\
\x74\x69\x6f\x6e\x3e\x31\x3c\x2f\x74\x69\x66\x66\x3a\x4f\x72\x69\
\x65\x6e\x74\x61\x74\x69\x6f\x6e\x3e\x0a\x20\x20\x20\x20\x20\x20\
\x3c\x2f\x72\x64\x66\x3a\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\
\x6e\x3e\x0a\x20\x20\x20\x3c\x2f\x72\x64\x66\x3a\x52\x44\x46\x3e\
\x0a\x3c\x2f\x78\x3a\x78\x6d\x70\x6d\x65\x74\x61\x3e\x0a\x4c\xc2\
\x27\x59\x00\x00\x10\x52\x49\x44\x41\x54\x68\x05\xb5\x5a\xf9\x6f\
\x1c\xd7\x7d\xff\xcc\xb5\x27\x97\x4b\x72\x79\x93\xa2\x28\x4a\x14\
\xa9\xfb\x3e\xec\x38\x75\x64\x3b\x76\xe4\xa2\x48\xe5\xd4\x6d\x6d\
\xa0\x01\xd2\xa6\x80\x8b\xa0\x40\xfa\x1f\xa4\x40\x52\xe4\xa7\xa2\
\x2d\xd2\x02\x6d\xd1\xfe\xd6\xc4\x31\x62\xa3\x8e\x61\xd9\xa8\x6d\
\x59\x97\x25\xd6\x94\x68\x91\x3a\x78\x88\x14\xaf\x15\x97\x4b\x72\
\x97\x7b\x5f\xb3\x33\xdb\xef\xf7\xcd\xcc\x72\x45\xd3\x29\x0c\x5b\
\x8f\xda\x7d\x6f\xbe\xef\x7b\x7c\xbe\xc7\x7b\x6f\x66\x56\x12\xa8\
\x25\x12\x99\x6f\xbb\x5c\x72\x48\x96\xe5\xcf\x3c\x1e\xcf\x24\xd3\
\xbe\x6a\xcb\xe5\x72\xdb\x4c\xe0\xa9\x62\xa9\x7c\x4c\x96\xa4\xac\
\x5b\x53\x6e\x99\xa6\x79\xbd\xae\xae\x6e\xf9\xab\xea\x66\xf9\x62\
\xb1\x38\xa8\xeb\xe6\x69\xc3\x28\x4d\x48\x4c\x88\x27\x52\x63\xc5\
\x72\x71\x97\x5b\x75\x4f\x6b\x9a\x7a\xc5\xa5\x2a\x1f\xbb\x5c\xae\
\xf7\x24\x32\xce\xf3\x5f\xa6\x91\xf2\x03\x65\xd3\xfc\x5e\xb1\xa4\
\x7f\x27\xaf\x17\x0e\x86\x0b\xab\x5e\x43\xaa\xa0\xd3\x15\x4a\x07\
\x5c\xbe\x51\x55\x56\x2f\x78\xdd\xda\x3b\xa4\xff\xc6\x97\xd1\xeb\
\xf0\x96\x4a\xa5\x23\xa4\xff\x95\x92\xae\x9f\x2d\xe8\xfa\x6e\x4d\
\x56\x2e\x0b\x27\x62\x89\x64\xe4\xa3\xe5\x91\xf6\x6e\x5f\x0b\xb6\
\xfb\xda\x4c\xaf\xcb\x1d\xd1\x14\xf5\x7f\xdd\x9a\x7a\xb5\xa2\x69\
\xff\xe3\x96\xa4\xbb\x8e\x92\xad\xfa\x4a\xa5\xa2\x94\xcb\xe5\xa7\
\x8a\xba\xfe\x22\x29\x7f\x36\xaf\x17\xf7\x2f\x15\xe3\xee\x07\xd9\
\x08\x46\xd3\x0f\x60\x48\x06\xf6\x78\x7a\xd0\x17\xe8\x44\x9b\xd6\
\x50\x6c\x72\xd7\x4f\x7a\xdc\xae\x0b\x94\xf9\x0f\x7c\x6e\xf7\x07\
\x14\x2c\x7d\x2b\xbd\xb5\xb4\x7c\xbe\xd2\x0b\xb9\xf4\xfd\x62\xa9\
\x74\xae\x5c\x36\xf6\x3d\xcc\xc7\xb4\xb9\x7c\x14\x4f\x35\xef\x1f\
\x77\x9c\x48\xfe\x68\xe4\x17\xf5\x3d\xe4\xc4\xd1\xe0\x6e\xf4\xf8\
\xd9\x99\xd6\x8a\x57\x75\x65\x5c\x2e\x6d\x42\x95\xe5\x11\x4d\xd3\
\xee\x90\xd1\x29\x52\xbc\x48\x1f\xce\x50\x80\x3e\xdd\x04\x7c\x6f\
\xd9\x30\x0e\x97\x4a\xfa\xa9\x42\xb9\xd4\x17\xce\xaf\x69\x73\x99\
\x28\xc6\x32\xb3\x98\x2b\x44\x61\x56\x88\x0b\xe2\x0b\x2d\xae\x20\
\xfa\x3d\x9d\x18\x08\x6e\x43\x9b\xab\xd1\x68\xf7\x34\x2c\x6a\x8a\
\x76\xc5\xe3\x52\x2e\x1a\x86\x71\x7e\xab\x52\xa3\x00\xb5\x53\x76\
\xff\xb4\x54\x2e\x9f\x2b\x95\x8d\x93\x2b\xb9\xb8\x67\x2e\xbf\x86\
\xd1\xc4\x0c\x26\xf2\x61\xfc\xfd\xbe\x1f\x3e\x54\x6d\x13\x6e\x83\
\x62\xb1\x90\x5f\xc7\x62\xee\x53\x74\xbb\x43\x38\xda\xd8\x27\xed\
\x08\x74\x04\xba\xbd\x4d\x27\xbc\xaa\xfb\x98\xcb\x30\xd2\x9a\xaa\
\xae\xa9\x8a\x16\x23\x99\x82\x61\x96\xbd\x04\x3e\x54\x28\x96\x5a\
\xf2\xe5\x62\x5d\x38\x17\x93\x18\xfc\xed\xf4\x3c\x66\x73\x0c\xde\
\x02\xee\xb8\x40\xd1\x46\x54\x4f\x21\x9a\x4d\x62\x34\xb5\x80\x7e\
\x6f\x87\x72\xa0\x61\x7b\x6f\x97\x2f\xb4\xbd\xcd\x68\x7c\x91\x02\
\xf5\x4a\x3a\x9b\x1f\xd2\x14\xe9\xaa\xdb\xed\xbe\x4e\x72\xdd\xc5\
\x72\xf9\x6c\x26\x97\x7b\xa1\xa4\x97\x4f\xc7\xf3\xa9\xc0\x6c\x7e\
\x15\x77\x62\xb3\x18\xcb\x2d\x20\x6f\x14\x21\xd1\x1f\x24\xc9\x23\
\x9c\xa0\xa1\x6a\x72\x42\x99\x46\xdd\xbc\xbe\x46\xce\xc4\xd1\xeb\
\x6b\xc5\x81\x60\x0f\x76\x04\xda\xe5\x6d\xfe\x50\xd0\xab\xb8\x82\
\x1e\xb7\xb9\x93\x81\x15\x4b\x45\x14\xca\x3a\xc2\xf9\x38\xa6\x93\
\x11\x8c\x25\xe6\x30\x9b\x5f\xb1\x82\xce\x0e\xb0\x22\xf1\x45\xe3\
\x8a\xc4\xff\xec\x6b\x20\x59\xce\x63\x38\x3f\x83\x7b\xc9\x30\x06\
\xeb\xba\xa4\xc1\x60\x77\xa8\xdb\x1f\x7a\xae\xd3\xdb\xf8\x4d\xb7\
\xaa\xbd\x6a\x18\x95\x71\x03\x95\x56\x5d\xd7\xf7\x64\x4a\xf9\xba\
\xd9\xcc\x0a\xee\xc6\xe7\x31\x92\x9a\x45\x9a\x64\x59\x6f\x85\x3e\
\xb4\xd4\x40\x99\x72\x09\x27\x88\x2a\x57\xca\x6c\x88\x27\xb9\x49\
\x30\x69\x34\x5d\x8a\x12\xc0\x15\x6c\xf7\x37\xe3\x70\xe3\x0e\xf4\
\x07\xdb\x41\xc6\x58\x10\x61\x72\x72\x2e\x19\xc5\x68\x72\x01\x33\
\xe9\x28\xd1\x68\x2f\xaa\x36\x81\xd8\xbe\xb2\xc6\xac\x97\x47\xc2\
\xb8\xdd\x67\xf4\x12\x6e\x14\x66\x71\x2b\xbe\x80\x5d\x81\x76\xec\
\x0f\xf6\xb8\x77\x04\x5b\xfb\xc8\x99\x3e\x52\x88\xb9\xd4\x0a\xa6\
\x49\xf7\xa7\x6b\xd3\x58\x29\xa5\x3e\x27\xcf\xfa\x28\xc3\x8a\x4a\
\x80\x7c\xf1\x64\x4a\x32\xc9\x09\xa2\x08\x80\x34\xc7\x93\xac\x87\
\x49\x78\xb0\xbe\x8a\xb9\x44\x9c\x16\x66\x0b\x0e\x34\xf5\x08\x9e\
\xdb\x89\x45\x32\xb2\x0a\xa3\x62\x08\x5e\xe2\x64\xb1\x2d\xe5\xd9\
\x69\x4e\xbd\xc8\x06\x47\x8f\x58\x85\x6e\xe6\xa7\x3f\x53\x32\x71\
\x2f\xb6\x84\x89\xf8\x32\x76\x06\xda\xb0\xb7\xa1\x0b\x92\x69\x62\
\x78\x65\x06\x0f\x4b\x49\xa1\xf3\x8b\xe4\x85\x13\xe9\x74\xba\x9b\
\x8d\x8b\x72\xe2\x01\x59\x18\x08\x75\x42\x95\x14\xdc\x8d\x2d\x8a\
\xc8\x71\x14\xd9\xd8\xfd\x78\x54\x7c\x68\x68\x63\x66\xe0\x94\x35\
\xdb\x59\xba\x10\xf2\x0c\x52\xd4\xab\x90\xb2\xb2\xeb\x5c\x33\xcb\
\xa1\xe6\x1e\x04\xdc\x5e\x44\x73\x49\xdc\x5f\x5f\x16\xf2\x5d\x81\
\x46\x78\x14\x0d\xe1\x64\x1c\x13\xb1\x48\x55\x9e\x35\x59\xe6\xd8\
\x16\x8f\xec\x8e\x03\x41\x73\xe4\xa1\xac\x96\x4c\xb3\x87\x0d\x38\
\x99\x60\xbe\x67\xbb\xf6\x83\xf6\x5f\xdc\x5e\x0e\x93\x0e\x3b\x23\
\x2c\x5c\x3b\xe6\x6b\xd1\x1c\x23\x74\xb1\xc9\x8e\x20\x90\x0c\xad\
\x25\xf8\x5d\x6e\xac\xe5\xd3\x02\xc7\xd9\xde\x23\xe8\x6f\x68\xc3\
\xc5\x85\x71\x4c\xae\x44\x45\x26\xcf\x6e\x3b\x84\x06\x8f\x1f\xc3\
\xcb\x0f\x88\x3e\x61\xc3\x25\x85\xb5\x36\x6b\xc7\xb6\x75\x82\x2b\
\xab\x8a\x2c\x77\x99\x14\x4a\xe1\x84\xc8\x37\x30\xb9\xca\x87\x2a\
\x39\xa6\xb3\x12\x1a\x12\x9d\xa7\xaa\x25\x20\xc6\x76\x75\x3b\x63\
\x32\xe0\x04\x4a\xc8\xb0\x11\xb2\x50\xef\xf1\xe0\xf7\xba\x77\x23\
\xe4\x0b\xe0\x8d\x7b\xc3\xc8\xd3\x3a\xf8\x2c\xb2\x80\xa5\x64\x12\
\x13\x6b\xb4\x8b\x51\x19\xd3\x89\x4e\x8b\x52\x81\x5b\x72\x21\xa4\
\xd5\x0b\x5a\x9d\xe6\xc6\xe1\x8e\x1e\xa4\x8a\x05\x8c\xaf\x2e\x41\
\x37\x69\xa9\x93\x3e\xb2\x62\x63\x71\x56\x17\x24\xb5\x62\x9a\xed\
\x55\xc0\xb6\x13\xef\x4d\x59\x67\x5b\x1d\x45\xaf\xa3\x2e\x88\xf9\
\xd4\x3a\x68\x9b\x13\xa8\x44\x0a\x19\x20\x35\x3b\x9d\x24\x2e\x0b\
\xfc\x0c\xa6\xb7\x21\x84\xd6\x3a\x3a\x42\x68\xbc\x92\x4e\x51\x06\
\x5c\xf8\xc3\xdd\xc7\x10\xcd\xa4\xa0\x56\x54\x02\xa8\xe3\x9d\xf1\
\x31\x21\x2d\x20\x11\x30\x93\x90\xbd\x7e\xe7\x06\x3a\xfc\x41\x2c\
\x65\xd6\x45\xf0\xfc\x6e\x0f\xfe\x6c\xdf\x93\x88\xe7\x73\xf8\xe7\
\xcc\x05\x2c\xd0\x9a\x64\x8b\x9f\xb3\x4f\x54\x95\x74\x34\x53\x6f\
\xaf\x09\x11\x56\xba\x12\xfe\x62\x77\x6b\x07\x9e\xef\xdf\x8b\x4b\
\xf3\xf7\xf1\xc9\xec\xb4\x4d\xb7\xe6\x2c\x1e\xfe\x26\x07\x04\xa9\
\x82\xbe\xe6\x56\x9c\x1b\x3c\x84\xfe\x50\x1b\x4d\xd0\x1a\x5a\x5b\
\xc5\xe8\xf2\x22\xae\xce\xcd\xe0\x6e\x74\x09\xeb\x69\x6b\x7b\x14\
\x29\xb2\xd3\xe5\xc8\xaf\x26\x33\x58\x4d\xd1\x19\xca\xe1\xa6\xb9\
\x44\xb6\x88\x2b\xb3\x33\x68\xa3\x80\xa8\x86\xb2\x81\xcf\xc6\xe6\
\xd8\x67\x66\xda\x62\xe5\x26\xe1\x21\x95\x8e\xb5\x40\xb9\xc6\x19\
\x15\x79\x08\x0d\xdd\xc1\x26\x9c\xe9\x1d\xc0\xd5\xa9\x19\x0b\x2c\
\xcf\x59\x76\xac\x7d\x9a\x22\x2e\xe2\x43\x9e\xbc\xb8\x7b\x3f\x95\
\xc0\x36\x21\xcb\x5f\x87\x3b\xba\x41\x07\x16\xfe\xe9\xca\x45\x50\
\xc6\x49\x9e\xf5\x7e\xb1\xbc\x50\x2b\xf4\x49\x74\x1e\x94\xf0\x5f\
\x23\xc3\x68\xf5\x07\x68\xb1\xaf\xa3\xc9\x57\x87\x90\xdf\x8f\x99\
\xd8\x1a\x0c\x83\xb6\x73\x52\xc3\xe7\x04\x37\xda\x62\xcd\x06\xde\
\x4e\xb9\x36\xad\x3f\x6b\x82\x11\xdf\x9c\x5f\x44\x0b\xd5\x32\x1d\
\xf7\x62\xde\xef\x76\xc1\xab\x69\x88\xe5\x72\x04\x8a\xa1\x5b\xad\
\x42\xbc\x8c\xaf\x9f\x32\xb1\xb9\x31\xcd\x28\xd9\x5e\x57\x27\xd9\
\x19\x2b\x16\xa2\xb7\xe5\x1f\xe1\x22\x5a\x81\x4a\x98\xef\x22\xb8\
\x1d\xef\xda\x8e\x13\x3d\xdb\xf1\xeb\x91\x11\x4c\xad\xd0\xa1\x4a\
\xcd\xb1\xaf\x12\xf2\x7a\x26\x88\x05\x46\x82\x8e\x22\xee\x73\x46\
\x19\x6f\x8d\x50\xfd\x0a\x6e\x09\x27\x77\xf5\xa2\xb7\xa9\x09\xf7\
\x96\x97\x71\x7d\x66\x5e\x44\xa3\xaf\x39\x84\xae\x86\x20\xd6\xb2\
\x59\x2c\x27\xd2\x68\xf2\xfa\x58\x5d\xb5\x31\x4d\x6c\x10\x4c\x61\
\xec\xb5\x06\x9c\x6b\x26\xdb\xe3\xda\x69\xc1\xcf\x72\x44\x74\x49\
\x1a\x76\xb7\xb4\xe2\x10\x65\x77\x62\x69\x75\x43\x0f\x69\x55\x29\
\x0b\x7e\x66\xaa\x88\x72\xa2\x88\xf2\xd8\x51\xce\x3d\x37\x5b\x73\
\x27\xed\xe5\xa7\x7a\x7b\xd1\x55\xdf\x80\x6b\x93\x0b\x82\xfe\xe2\
\xbe\xbd\x38\xb5\x63\x3b\xae\xde\x9f\xc5\xfb\xb7\x27\xc5\x42\x6e\
\x0f\xf2\xbd\x21\xb0\x9c\x4c\x13\x8d\xb6\x4b\x7b\x97\x13\x87\x1e\
\xa7\xcc\xb6\xe1\x94\x83\x03\x56\x98\xf9\x02\xfb\x97\x27\x66\xd1\
\xe4\xf1\x61\x7a\x39\x86\x4a\xc9\xda\x2d\x1d\x79\x55\xaa\x48\x3e\
\x2e\x0c\x8e\x96\x2c\xd0\x5b\x98\x05\x0a\xfe\x72\x1c\xa2\xe1\x07\
\xb7\xef\x23\x99\x2e\x20\x55\x28\x56\xa3\x9b\x48\x15\x70\x63\x26\
\x8c\xbb\x0b\x51\x5c\xbf\xbf\x40\x19\x05\x06\xda\x9b\x85\xdf\x93\
\xcb\xab\x18\x9e\x5d\x84\x69\x38\xd1\xb0\x32\xed\xe8\x66\xd0\xa2\
\xd5\xd8\xe0\xeb\x2a\x9d\x2f\xec\xb9\xd5\xf5\x1c\xfe\xe3\xd2\x0d\
\xf0\x71\x60\x11\x37\xf8\x54\x5a\x6e\x3e\xc1\xc7\xf7\x4e\x5c\x4e\
\x8e\x3d\xa1\x69\x43\x80\x45\xc2\x2b\x29\x84\xa3\xbc\xfd\x12\xdd\
\x8e\xe8\xaf\x3e\x19\x03\x1d\x36\xc8\x16\x4a\x6c\x12\xd7\x26\xe6\
\xc5\xc7\x42\xe2\xc8\xdb\x48\x6c\x99\x5a\x79\xb1\xb2\x88\xee\x44\
\xf5\x77\xd9\x07\xd9\x39\xd9\xd7\x8d\xf5\x6c\x0e\x53\x91\xb5\xaa\
\x17\x2a\xa9\x77\xb3\xf1\x4a\x99\xb6\x4a\xa1\x81\xd4\x72\x6f\x3b\
\xc3\x8b\xdd\x82\xb2\x11\xc5\x0d\x1a\x90\xa7\x45\x0f\x94\x89\xcb\
\x16\x20\xee\x2f\x23\x6f\xed\x84\x8e\xb3\x02\xc9\x17\xca\xbb\xe9\
\xb6\xe4\x87\x67\x4e\xe2\x41\x34\x86\x9f\xbd\x79\xb1\x6a\x91\x17\
\xb6\x8b\x51\x8a\xc5\x47\xe0\xa9\xbc\xac\x20\xb2\x3e\x9a\x64\xf5\
\x96\xcb\x9b\xc7\x1c\x43\xdb\x59\x62\x72\xc6\x8f\x53\xde\x54\x00\
\x9d\x82\x16\xf4\x7a\x69\x9d\xd9\x36\x09\x1d\x67\x42\x63\xa0\xbc\
\xf8\xc4\xdd\x34\xa5\xd6\x3e\x6f\x84\xa7\x22\xdd\x15\xfb\x40\x63\
\x7f\x44\xb3\x1d\x12\x99\x23\x02\x27\x41\x8c\x49\xd3\x63\x94\xcf\
\x66\xcb\x78\xf3\xea\x3d\xba\x7b\xa0\x45\xc0\x77\xdd\xb6\x7d\x3a\
\x27\xe8\x44\xe3\x26\xd6\x84\x75\xfb\x20\xae\xe9\xcb\xca\x02\x23\
\xdc\xdc\x2c\x27\x9c\xb2\x62\x2f\x84\x1f\xf4\x6d\xc9\x58\xfc\x5f\
\xb7\x3c\xeb\x7b\x7f\x68\xa6\x6a\xcb\x29\x61\x7e\x28\xa2\x24\x59\
\xe7\x84\xc2\xa5\x64\xa7\x41\xac\x41\x1a\x5b\xcf\x15\x5c\x2c\x34\
\x67\x47\xdc\x09\xba\x55\x6e\xa4\x9a\xa3\x4f\x3a\xc4\xf4\x63\x94\
\x57\x35\x19\x4f\xec\xeb\xc2\x6a\x22\x8f\xf1\x39\x3a\x2b\x84\x45\
\xbe\xb3\xa0\x27\x23\xae\x9f\x0a\x6d\x8d\x15\xba\x13\xe3\x45\x29\
\x1c\x20\x16\x51\xe7\xd4\x73\xe3\xf1\x06\x50\xc6\x6d\x5d\xb3\x22\
\xe1\x38\x0b\x31\xdf\x63\x94\x6f\x0a\xfa\xf0\xda\x77\x8f\x63\xf4\
\x7e\x14\xe3\xd3\x74\x5e\x08\x8b\xec\x04\xbd\x6e\x11\x63\x5e\x13\
\x0a\x01\xa1\x19\x67\x52\x80\x12\xe1\xe5\x11\x0d\x78\xda\x8e\x74\
\x95\xa7\x66\xde\xc9\x50\x75\x8e\xa4\x1c\xda\xd7\x21\xcf\xa7\xb6\
\x69\x54\x50\x2c\xd0\x9a\xe0\x03\xd4\x6e\x7c\x62\x4b\x02\x18\xaf\
\x09\xfb\x50\xb2\x83\x4a\x80\xc9\xb4\x00\x6e\x67\x47\x38\x20\xdb\
\x34\x6b\x52\xf8\xe0\x44\xdf\x46\xff\xb8\xe4\xa3\xd1\x2c\xde\xfe\
\xe8\x3e\xee\x4c\xaf\x89\xdd\x49\x00\x21\x47\xf8\x19\x5b\x66\x87\
\xd8\x33\x49\xb6\xbc\x7b\x24\x92\xb6\xb7\x16\x8d\x57\x86\x93\xa9\
\x47\x79\xb7\x92\x11\x7a\xbf\x46\x79\x89\x0e\xbb\x37\xde\x9d\xb4\
\x2b\x45\xc0\x16\xda\x29\x13\x5c\x22\x96\x13\xa0\x72\xda\x88\xbe\
\x28\x00\x0b\x82\xcd\x53\xe5\x25\xaa\xe5\x82\x8d\x70\xd3\xbc\xc3\
\x57\xe5\xd9\x34\xcf\x0e\x57\xe7\x58\xc5\xa6\xf9\xad\xe4\x7b\x3a\
\x03\x38\x79\xa4\x13\xd7\x6f\x3e\x44\x38\x92\x79\x44\x9e\xb7\x58\
\x4b\x1f\x97\x13\x2d\x6c\x47\xb9\xd3\xb3\x0d\x51\x45\xb5\x04\x26\
\x52\xdb\x0c\xa6\xf6\xba\x96\xfd\xeb\x90\x3f\xfb\x74\x1f\xbe\xfb\
\xc2\x2e\xac\x46\x73\x08\x2f\xf2\x0b\x48\xcb\x3e\xf7\xce\x7b\x27\
\x48\xe4\x04\xef\x38\x4e\x54\x78\xd2\x69\x02\x10\x7f\x71\xc6\xa8\
\x63\x57\xc5\x79\x4d\x34\x01\x90\x69\xf6\xfc\xd7\x21\xcf\x38\x76\
\xed\x08\xa2\xa3\xbd\x4e\xe0\x89\x2c\x67\xe1\x56\x14\xdc\x9b\x8c\
\x61\x6e\x36\x0d\x49\xe7\xf3\x8c\xa2\x2f\x80\x59\x4e\x58\x43\xde\
\x9d\x6c\x27\x6a\x2b\xdf\x71\x84\x13\xc6\xd0\x2d\x66\x1e\x4b\xf0\
\x78\x14\xd4\xd7\xbb\xb0\xb2\xc2\x8f\x9d\x15\x04\x02\x2e\x0c\x0e\
\x34\x89\x3b\xcd\x3b\x77\xd7\xe8\xf5\x3b\xdf\x57\x59\x6d\x2b\x79\
\x96\x11\xbe\xdb\x29\x74\x32\xc6\x3a\x5e\x7d\x79\x40\xe8\x62\x9e\
\x89\xa9\x75\x5c\x1b\x8a\xe0\xe3\x4b\x61\xcc\x93\x13\x56\x18\x85\
\xa4\x50\x5e\xcd\x04\xc8\x3b\x67\x61\x0b\x17\xed\xbd\x51\xa6\xc5\
\xbe\x73\x27\x45\xa5\x83\xa3\x52\x41\x24\x92\xc5\xcc\x74\x12\xf5\
\x41\x37\x9e\x39\xd3\x85\xb6\x36\x3f\x7e\xfb\xdb\x07\x78\xf8\x30\
\x8b\xbe\x6d\x0d\x78\xed\x07\x07\xe8\xfe\xc6\xc4\xdf\xfd\x7c\x18\
\x0b\x0b\x69\xb4\xb6\xfa\x48\xbe\x9e\x7e\x03\xd1\x31\x3e\x41\x0f\
\xfb\x0e\xd2\x2a\x06\x2b\x64\x8c\x86\x83\xc4\xed\xa5\x3f\xe8\xc7\
\xf1\xa3\xf4\x9c\x6e\xb7\xe3\x47\xda\x50\xc8\x9b\x78\xef\xfc\xbc\
\x08\x9e\x48\x4f\x55\x7e\x53\x39\xf1\x5b\x0b\x11\x1a\x12\x76\x78\
\x06\xf6\x34\xe1\x95\x57\xfb\x31\x38\x48\x8f\xe2\x44\x9c\x18\x5f\
\xc7\xaf\x7e\x39\x05\x83\x80\xbe\xfc\xd2\x6e\xe4\xb2\x3a\xae\x5d\
\x8c\x60\x69\x3e\x87\x52\xd6\xc0\x83\xe9\x94\xc8\x84\xcc\x77\x6b\
\x54\xa2\xfb\x06\x42\xf8\xc1\x9f\xef\xc1\xd2\x52\x16\x3f\xfb\xe9\
\x0d\xa4\xd3\x25\x04\xea
Download .txt
gitextract_54u4k5gb/

├── .gitignore
├── LICENSE
├── README.md
├── src/
│   └── offlinemapmatching/
│       ├── __init__.py
│       ├── help_docs/
│       │   ├── help.html
│       │   ├── help_processing_clipping_network.html
│       │   ├── help_processing_match_trajectory.html
│       │   └── help_processing_reduce_density.html
│       ├── i18n/
│       │   └── af.ts
│       ├── metadata.txt
│       ├── mm/
│       │   ├── __init__.py
│       │   ├── helper/
│       │   │   ├── __init__.py
│       │   │   └── measurement_statistics.py
│       │   ├── hidden_states/
│       │   │   ├── __init__.py
│       │   │   ├── candidate.py
│       │   │   ├── hidden_model.py
│       │   │   └── transition.py
│       │   ├── map_matcher.py
│       │   └── observation/
│       │       ├── __init__.py
│       │       ├── intersection.py
│       │       ├── network.py
│       │       ├── observation.py
│       │       └── trajectory.py
│       ├── mm_processing/
│       │   ├── clip_network_algorithm.py
│       │   ├── offline_map_matching_algorithm.py
│       │   ├── offline_map_matching_provider.py
│       │   └── reduce_trajectory_density.py
│       ├── offline_map_matching.py
│       ├── offline_map_matching_dialog.py
│       ├── offline_map_matching_dialog_base.ui
│       ├── pb_tool.cfg
│       ├── resources.py
│       ├── resources.qrc
│       └── style.qml
└── testdata/
    ├── network.geojson
    ├── trajectory1.geojson
    └── trajectory2.geojson
Download .txt
SYMBOL INDEX (117 symbols across 17 files)

FILE: src/offlinemapmatching/__init__.py
  function classFactory (line 28) | def classFactory(iface):  # pylint: disable=invalid-name

FILE: src/offlinemapmatching/mm/helper/measurement_statistics.py
  class MeasurementStatistics (line 3) | class MeasurementStatistics:
    method __init__ (line 5) | def __init__(self):
    method addMeasurement (line 8) | def addMeasurement(self, value):
    method getMeanValue (line 11) | def getMeanValue(self):
    method getStandardDeviation (line 14) | def getStandardDeviation(self):

FILE: src/offlinemapmatching/mm/hidden_states/candidate.py
  class Candidate (line 4) | class Candidate:
    method __init__ (line 6) | def __init__(self, point, distance, observation_id):
    method getEmissionProbability (line 11) | def getEmissionProbability(self, sigma, my):

FILE: src/offlinemapmatching/mm/hidden_states/hidden_model.py
  class HiddenModel (line 12) | class HiddenModel:
    method __init__ (line 14) | def __init__(self, trajectory, network):
    method createGraph (line 27) | def createGraph(self, maximum_distance):
    method createBacktracking (line 85) | def createBacktracking(self):
    method findViterbiPath (line 125) | def findViterbiPath(self):
    method setTransitions (line 168) | def setTransitions(self, fast_map_matching=False):
    method candidatesHaveDifferentPositions (line 214) | def candidatesHaveDifferentPositions(self, candidate_1, candidate_2):
    method setStartingProbabilities (line 227) | def setStartingProbabilities(self):
    method addFeaturesToLayer (line 241) | def addFeaturesToLayer(self, features, attributes, crs):
    method getPathOnNetwork (line 265) | def getPathOnNetwork(self, vertices, field_array):
    method initProgressbar (line 313) | def initProgressbar(self, maximum):
    method updateProgressbar (line 319) | def updateProgressbar(self):
    method initFeedback (line 325) | def initFeedback(self, maximum):
    method updateFeedback (line 333) | def updateFeedback(self):

FILE: src/offlinemapmatching/mm/hidden_states/transition.py
  class Transition (line 4) | class Transition:
    method __init__ (line 6) | def __init__(self, start_candidate, end_candidate, network, different_...
    method setDirectionProbability (line 16) | def setDirectionProbability(self, start_observation, end_observation):
    method setRoutingProbability (line 61) | def setRoutingProbability(self, distance_between_observations, beta):
    method setTransitionProbability (line 76) | def setTransitionProbability(self):
    method getAllpoints_on_network (line 84) | def getAllpoints_on_network(self, network):
    method getLengthOfTransition (line 88) | def getLengthOfTransition(self):
    method getDistanceOfBeeline (line 103) | def getDistanceOfBeeline(self):

FILE: src/offlinemapmatching/mm/map_matcher.py
  class MapMatcher (line 7) | class MapMatcher:
    method __init__ (line 9) | def __init__(self):
    method startViterbiMatchingGui (line 16) | def startViterbiMatchingGui(self, pb, trajectory_name, network_name, a...
    method startViterbiMatchingProcessing (line 84) | def startViterbiMatchingProcessing(self, trajectory_name, network_name...
    method fillLayerComboBox (line 184) | def fillLayerComboBox(self, iface, combobox, geom_type):
    method fillAttributeComboBox (line 199) | def fillAttributeComboBox(self, combobox, layername):
    method getLayer (line 212) | def getLayer(self, layername):
    method setUp (line 218) | def setUp(self, line_layer, point_layer, point_attr, feedback):
    method defineAttributes (line 234) | def defineAttributes(self):

FILE: src/offlinemapmatching/mm/observation/intersection.py
  class Intersection (line 3) | class Intersection:
    method __init__ (line 5) | def __init__(self, geometry, edge_ids):

FILE: src/offlinemapmatching/mm/observation/network.py
  class Network (line 6) | class Network:
    method __init__ (line 9) | def __init__(self, linestring_layer):
    method extractAllIntersections (line 15) | def extractAllIntersections(self):
    method routing (line 52) | def routing(self, start, end):

FILE: src/offlinemapmatching/mm/observation/observation.py
  class Observation (line 6) | class Observation:
    method __init__ (line 8) | def __init__(self, point, id):
    method getAllCandidates (line 12) | def getAllCandidates(self, network, max_distance):
    method getCandidates (line 27) | def getCandidates(self, network, max_distance):
    method isIntersectionInSearchDistance (line 85) | def isIntersectionInSearchDistance(self, edges, candidates):

FILE: src/offlinemapmatching/mm/observation/trajectory.py
  class Trajectory (line 4) | class Trajectory:
    method __init__ (line 6) | def __init__(self, point_layer, id_field):
    method extractObservations (line 11) | def extractObservations(self, point_layer, id_field):

FILE: src/offlinemapmatching/mm_processing/clip_network_algorithm.py
  class ClipNetworkAlgorithm (line 49) | class ClipNetworkAlgorithm(QgsProcessingAlgorithm):
    method initAlgorithm (line 73) | def initAlgorithm(self, config):
    method processAlgorithm (line 121) | def processAlgorithm(self, parameters, context, feedback):
    method name (line 221) | def name(self):
    method helpUrl (line 231) | def helpUrl(self):
    method shortHelpString (line 241) | def shortHelpString(self):
    method displayName (line 251) | def displayName(self):
    method group (line 258) | def group(self):
    method groupId (line 265) | def groupId(self):
    method tr (line 275) | def tr(self, string):
    method createInstance (line 278) | def createInstance(self):
    method icon (line 281) | def icon(self):

FILE: src/offlinemapmatching/mm_processing/offline_map_matching_algorithm.py
  class OfflineMapMatchingAlgorithm (line 49) | class OfflineMapMatchingAlgorithm(QgsProcessingAlgorithm):
    method initAlgorithm (line 74) | def initAlgorithm(self, config):
    method processAlgorithm (line 130) | def processAlgorithm(self, parameters, context, feedback):
    method name (line 210) | def name(self):
    method helpUrl (line 220) | def helpUrl(self):
    method shortHelpString (line 230) | def shortHelpString(self):
    method displayName (line 240) | def displayName(self):
    method group (line 247) | def group(self):
    method groupId (line 254) | def groupId(self):
    method tr (line 264) | def tr(self, string):
    method createInstance (line 267) | def createInstance(self):
    method icon (line 270) | def icon(self):

FILE: src/offlinemapmatching/mm_processing/offline_map_matching_provider.py
  class OfflineMapMatchingProvider (line 41) | class OfflineMapMatchingProvider(QgsProcessingProvider):
    method __init__ (line 43) | def __init__(self):
    method unload (line 52) | def unload(self):
    method loadAlgorithms (line 59) | def loadAlgorithms(self):
    method id (line 66) | def id(self):
    method icon (line 74) | def icon(self):
    method name (line 77) | def name(self):
    method longName (line 86) | def longName(self):

FILE: src/offlinemapmatching/mm_processing/reduce_trajectory_density.py
  class ReduceTrajectoryDensity (line 50) | class ReduceTrajectoryDensity(QgsProcessingAlgorithm):
    method initAlgorithm (line 73) | def initAlgorithm(self, config):
    method processAlgorithm (line 112) | def processAlgorithm(self, parameters, context, feedback):
    method name (line 165) | def name(self):
    method helpUrl (line 175) | def helpUrl(self):
    method shortHelpString (line 185) | def shortHelpString(self):
    method displayName (line 195) | def displayName(self):
    method group (line 202) | def group(self):
    method groupId (line 209) | def groupId(self):
    method tr (line 219) | def tr(self, string):
    method createInstance (line 222) | def createInstance(self):
    method icon (line 225) | def icon(self):
    method reduceDensity (line 228) | def reduceDensity(self, startIndex, nextIndex, layer, distance, output...

FILE: src/offlinemapmatching/offline_map_matching.py
  class OfflineMapMatching (line 46) | class OfflineMapMatching:
    method __init__ (line 49) | def __init__(self, iface):
    method tr (line 107) | def tr(self, message):
    method add_action (line 122) | def add_action(
    method initGui (line 199) | def initGui(self):
    method clipNetwork (line 243) | def clipNetwork(self):
    method reduceDensity (line 246) | def reduceDensity(self):
    method fastTrajectoryMatching (line 249) | def fastTrajectoryMatching(self):
    method matchTrajectory (line 252) | def matchTrajectory(self):
    method unload (line 255) | def unload(self):
    method run (line 270) | def run(self):
    method populateComboBox (line 296) | def populateComboBox(self, type):
    method startPopulateFieldsComboBox (line 305) | def startPopulateFieldsComboBox(self):
    method startMapMatching (line 308) | def startMapMatching(self):

FILE: src/offlinemapmatching/offline_map_matching_dialog.py
  class OfflineMapMatchingDialog (line 34) | class OfflineMapMatchingDialog(QtWidgets.QDialog, FORM_CLASS):
    method __init__ (line 35) | def __init__(self, parent=None):

FILE: src/offlinemapmatching/resources.py
  function qInitResources (line 470) | def qInitResources():
  function qCleanupResources (line 473) | def qCleanupResources():
Condensed preview — 37 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (973K chars).
[
  {
    "path": ".gitignore",
    "chars": 361,
    "preview": "# Build and Release Folders\nbin-debug/\nbin-release/\n[Oo]bj/\n[Bb]in/\n\n# Other files and folders\n.settings/\n\n# Executables"
  },
  {
    "path": "LICENSE",
    "chars": 35147,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
  },
  {
    "path": "README.md",
    "chars": 18443,
    "preview": "# Offline-MapMatching\r\nA <a href=\"https://github.com/qgis/QGIS\">QGIS</a>-plugin for matching a trajectory with a network"
  },
  {
    "path": "src/offlinemapmatching/__init__.py",
    "chars": 1699,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\n/***************************************************************************\n OfflineMapMatc"
  },
  {
    "path": "src/offlinemapmatching/help_docs/help.html",
    "chars": 4997,
    "preview": "<html>\n    <body>\n        <h2><font face=\"helvetica, arial\">Offline-MapMatching</font></h2>\n        <p>This plugin match"
  },
  {
    "path": "src/offlinemapmatching/help_docs/help_processing_clipping_network.html",
    "chars": 1708,
    "preview": "<html>\n    <body>\n        <p><font face=\"helvetica, arial\">This algorithm gives the possibility to clip a network layer "
  },
  {
    "path": "src/offlinemapmatching/help_docs/help_processing_match_trajectory.html",
    "chars": 2664,
    "preview": "<html>\n    <body>\n        <p><font face=\"helvetica, arial\">This algorithm matches a trajectory on a network using a Hidd"
  },
  {
    "path": "src/offlinemapmatching/help_docs/help_processing_reduce_density.html",
    "chars": 1359,
    "preview": "<html>\n    <body>\n        <p><font face=\"helvetica, arial\">This algorithm reduces the density of a given trajectory, i.e"
  },
  {
    "path": "src/offlinemapmatching/i18n/af.ts",
    "chars": 333,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS><TS version=\"2.0\" language=\"af\" sourcelanguage=\"en\">\n<context>\n    <"
  },
  {
    "path": "src/offlinemapmatching/metadata.txt",
    "chars": 3255,
    "preview": "# This file contains metadata for your plugin. Since \n# version 2.0 of QGIS this is the proper way to supply \n# informat"
  },
  {
    "path": "src/offlinemapmatching/mm/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/offlinemapmatching/mm/helper/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/offlinemapmatching/mm/helper/measurement_statistics.py",
    "chars": 386,
    "preview": "import statistics\r\n\r\nclass MeasurementStatistics:\r\n    \r\n    def __init__(self):\r\n        self.measurments = []\r\n    \r\n "
  },
  {
    "path": "src/offlinemapmatching/mm/hidden_states/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/offlinemapmatching/mm/hidden_states/candidate.py",
    "chars": 475,
    "preview": "import math\r\nfrom ..observation.observation import *\r\n\r\nclass Candidate:\r\n    \r\n    def __init__(self, point, distance, "
  },
  {
    "path": "src/offlinemapmatching/mm/hidden_states/hidden_model.py",
    "chars": 17474,
    "preview": "from ..observation.network import *\r\nfrom ..observation.trajectory import *\r\nfrom ..observation.observation import *\r\nfr"
  },
  {
    "path": "src/offlinemapmatching/mm/hidden_states/transition.py",
    "chars": 5334,
    "preview": "from .candidate import *\r\nimport math\r\n\r\nclass Transition:\r\n    \r\n    def __init__(self, start_candidate, end_candidate,"
  },
  {
    "path": "src/offlinemapmatching/mm/map_matcher.py",
    "chars": 11647,
    "preview": "from PyQt5.QtWidgets import QProgressBar, QComboBox, QLabel, QApplication\r\nfrom qgis.core import *\r\nfrom .hidden_states."
  },
  {
    "path": "src/offlinemapmatching/mm/observation/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/offlinemapmatching/mm/observation/intersection.py",
    "chars": 181,
    "preview": "from qgis.core import *\r\n\r\nclass Intersection:\r\n    \r\n    def __init__(self, geometry, edge_ids):\r\n        self.geometry"
  },
  {
    "path": "src/offlinemapmatching/mm/observation/network.py",
    "chars": 4510,
    "preview": "from qgis.analysis import *\r\nfrom qgis.core import *\r\nfrom .intersection import *\r\nimport processing\r\n\r\nclass Network:\r\n"
  },
  {
    "path": "src/offlinemapmatching/mm/observation/observation.py",
    "chars": 4839,
    "preview": "from .network import *\r\nfrom .intersection import *\r\nfrom ..hidden_states.candidate import *\r\nfrom qgis.core import *\r\n\r"
  },
  {
    "path": "src/offlinemapmatching/mm/observation/trajectory.py",
    "chars": 687,
    "preview": "from .observation import *\r\nfrom qgis.core import *\r\n\r\nclass Trajectory:\r\n    \r\n    def __init__(self, point_layer, id_f"
  },
  {
    "path": "src/offlinemapmatching/mm_processing/clip_network_algorithm.py",
    "chars": 10209,
    "preview": "# -*- coding: utf-8 -*-\r\n\r\n'''\r\n/***************************************************************************\r\n Offline-M"
  },
  {
    "path": "src/offlinemapmatching/mm_processing/offline_map_matching_algorithm.py",
    "chars": 10036,
    "preview": "# -*- coding: utf-8 -*-\r\n\r\n'''\r\n/***************************************************************************\r\n Offline-M"
  },
  {
    "path": "src/offlinemapmatching/mm_processing/offline_map_matching_provider.py",
    "chars": 3478,
    "preview": "# -*- coding: utf-8 -*-\r\n\r\n'''\r\n/***************************************************************************\r\n OfflineMa"
  },
  {
    "path": "src/offlinemapmatching/mm_processing/reduce_trajectory_density.py",
    "chars": 9622,
    "preview": "# -*- coding: utf-8 -*-\r\n\r\n'''\r\n/***************************************************************************\r\n Offline-M"
  },
  {
    "path": "src/offlinemapmatching/offline_map_matching.py",
    "chars": 14842,
    "preview": "# -*- coding: utf-8 -*-\r\n'''\r\n/***************************************************************************\r\n OfflineMapM"
  },
  {
    "path": "src/offlinemapmatching/offline_map_matching_dialog.py",
    "chars": 2014,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\n/***************************************************************************\n OfflineMapMatc"
  },
  {
    "path": "src/offlinemapmatching/offline_map_matching_dialog_base.ui",
    "chars": 10664,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<ui version=\"4.0\">\r\n <class>OfflineMapMatchingDialogBase</class>\r\n <widget class"
  },
  {
    "path": "src/offlinemapmatching/pb_tool.cfg",
    "chars": 3125,
    "preview": "#/***************************************************************************\r\n# OfflineMapMatching\r\n#\r\n# Configuration "
  },
  {
    "path": "src/offlinemapmatching/resources.py",
    "chars": 27873,
    "preview": "# -*- coding: utf-8 -*-\n\n# Resource object code\n#\n# Created by: The Resource Compiler for PyQt5 (Qt v5.14.0)\n#\n# WARNING"
  },
  {
    "path": "src/offlinemapmatching/resources.qrc",
    "chars": 268,
    "preview": "<RCC>\n    <qresource prefix=\"/plugins/offline_map_matching\" >\n        <file>icons/icon.png</file>\n        <file>icons/cl"
  },
  {
    "path": "src/offlinemapmatching/style.qml",
    "chars": 6403,
    "preview": "<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>\n<qgis version=\"3.2.0-Bonn\" hasScaleBasedVisibilityFlag=\"0\" mi"
  },
  {
    "path": "testdata/network.geojson",
    "chars": 689067,
    "preview": "{\n\"type\": \"FeatureCollection\",\n\"name\": \"network\",\n\"crs\": { \"type\": \"name\", \"properties\": { \"name\": \"urn:ogc:def:crs:EPSG"
  },
  {
    "path": "testdata/trajectory1.geojson",
    "chars": 3248,
    "preview": "{\n\"type\": \"FeatureCollection\",\n\"name\": \"trajectory1\",\n\"crs\": { \"type\": \"name\", \"properties\": { \"name\": \"urn:ogc:def:crs:"
  },
  {
    "path": "testdata/trajectory2.geojson",
    "chars": 3226,
    "preview": "{\n\"type\": \"FeatureCollection\",\n\"name\": \"trajectory2\",\n\"crs\": { \"type\": \"name\", \"properties\": { \"name\": \"urn:ogc:def:crs:"
  }
]

About this extraction

This page contains the full source code of the jagodki/Offline-MapMatching GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 37 files (888.3 KB), approximately 315.4k tokens, and a symbol index with 117 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!